Merge "Fix task animations"
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
index 2c84db1..40778de 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -150,6 +150,8 @@
final Bundle status = new Bundle();
status.putLong(key + "_median", mStats.getMedian());
status.putLong(key + "_mean", (long) mStats.getMean());
+ status.putLong(key + "_percentile90", mStats.getPercentile90());
+ status.putLong(key + "_percentile95", mStats.getPercentile95());
status.putLong(key + "_stddev", (long) mStats.getStandardDeviation());
instrumentation.sendStatus(Activity.RESULT_OK, status);
}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
index acc44a8..5e50073 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
@@ -21,7 +21,7 @@
import java.util.List;
public class Stats {
- private long mMedian, mMin, mMax;
+ private long mMedian, mMin, mMax, mPercentile90, mPercentile95;
private double mMean, mStandardDeviation;
/* Calculate stats in constructor. */
@@ -35,12 +35,14 @@
Collections.sort(values);
- mMedian = size % 2 == 0 ? (values.get(size / 2) + values.get(size / 2 - 1)) / 2 :
- values.get(size / 2);
-
mMin = values.get(0);
mMax = values.get(values.size() - 1);
+ mMedian = size % 2 == 0 ? (values.get(size / 2) + values.get(size / 2 - 1)) / 2 :
+ values.get(size / 2);
+ mPercentile90 = getPercentile(values, 90);
+ mPercentile95 = getPercentile(values, 95);
+
for (int i = 0; i < size; ++i) {
long result = values.get(i);
mMean += result;
@@ -73,4 +75,21 @@
public double getStandardDeviation() {
return mStandardDeviation;
}
+
+ public long getPercentile90() {
+ return mPercentile90;
+ }
+
+ public long getPercentile95() {
+ return mPercentile95;
+ }
+
+ private static long getPercentile(List<Long> values, int percentile) {
+ if (percentile < 0 || percentile > 100) {
+ throw new IllegalArgumentException(
+ "invalid percentile " + percentile + ", should be 0-100");
+ }
+ int idx = (values.size() - 1) * percentile / 100;
+ return values.get(idx);
+ }
}
diff --git a/api/current.txt b/api/current.txt
index 453eac5..9ceb01a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4070,7 +4070,7 @@
method public android.app.ActivityOptions setAppVerificationBundle(android.os.Bundle);
method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
method public android.app.ActivityOptions setLaunchDisplayId(int);
- method public android.app.ActivityOptions setLockTaskMode(boolean);
+ method public android.app.ActivityOptions setLockTaskEnabled(boolean);
method public android.os.Bundle toBundle();
method public void update(android.app.ActivityOptions);
field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -6678,7 +6678,7 @@
field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20
field public static final int LOCK_TASK_FEATURE_NONE = 0; // 0x0
field public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 2; // 0x2
- field public static final int LOCK_TASK_FEATURE_RECENTS = 8; // 0x8
+ field public static final int LOCK_TASK_FEATURE_OVERVIEW = 8; // 0x8
field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2
field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
@@ -11389,6 +11389,8 @@
ctor public PermissionInfo();
ctor public PermissionInfo(android.content.pm.PermissionInfo);
method public int describeContents();
+ method public int getProtection();
+ method public int getProtectionFlags();
method public java.lang.CharSequence loadDescription(android.content.pm.PackageManager);
field public static final android.os.Parcelable.Creator<android.content.pm.PermissionInfo> CREATOR;
field public static final int FLAG_COSTS_MONEY = 1; // 0x1
@@ -11405,8 +11407,8 @@
field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800
field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10
field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200
- field public static final int PROTECTION_MASK_BASE = 15; // 0xf
- field public static final int PROTECTION_MASK_FLAGS = 65520; // 0xfff0
+ field public static final deprecated int PROTECTION_MASK_BASE = 15; // 0xf
+ field public static final deprecated int PROTECTION_MASK_FLAGS = 65520; // 0xfff0
field public static final int PROTECTION_NORMAL = 0; // 0x0
field public static final int PROTECTION_SIGNATURE = 2; // 0x2
field public static final deprecated int PROTECTION_SIGNATURE_OR_SYSTEM = 3; // 0x3
@@ -11414,7 +11416,7 @@
field public int flags;
field public java.lang.String group;
field public java.lang.CharSequence nonLocalizedDescription;
- field public int protectionLevel;
+ field public deprecated int protectionLevel;
}
public final class ProviderInfo extends android.content.pm.ComponentInfo implements android.os.Parcelable {
@@ -21903,6 +21905,7 @@
field public static final int TYPE_FM_TUNER = 16; // 0x10
field public static final int TYPE_HDMI = 9; // 0x9
field public static final int TYPE_HDMI_ARC = 10; // 0xa
+ field public static final int TYPE_HEARING_AID = 23; // 0x17
field public static final int TYPE_IP = 20; // 0x14
field public static final int TYPE_LINE_ANALOG = 5; // 0x5
field public static final int TYPE_LINE_DIGITAL = 6; // 0x6
@@ -41818,12 +41821,12 @@
public class MbmsDownloadSession implements java.lang.AutoCloseable {
method public int cancelDownload(android.telephony.mbms.DownloadRequest);
method public void close();
- method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, android.os.Handler);
- method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, int, android.os.Handler);
+ method public static android.telephony.MbmsDownloadSession create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsDownloadSessionCallback);
+ method public static android.telephony.MbmsDownloadSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsDownloadSessionCallback);
method public int download(android.telephony.mbms.DownloadRequest);
method public java.io.File getTempFileRootDirectory();
method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads();
- method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
+ method public int registerStateCallback(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadStateCallback);
method public void requestDownloadState(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo);
method public void requestUpdateFileServices(java.util.List<java.lang.String>);
method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest);
@@ -41851,10 +41854,10 @@
public class MbmsStreamingSession implements java.lang.AutoCloseable {
method public void close();
- method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, int, android.os.Handler);
- method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler);
+ method public static android.telephony.MbmsStreamingSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsStreamingSessionCallback);
+ method public static android.telephony.MbmsStreamingSession create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsStreamingSessionCallback);
method public void requestUpdateStreamingServices(java.util.List<java.lang.String>);
- method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler);
+ method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback);
}
public class NeighboringCellInfo implements android.os.Parcelable {
@@ -42727,20 +42730,23 @@
package android.telephony.mbms {
public final class DownloadRequest implements android.os.Parcelable {
- method public static android.telephony.mbms.DownloadRequest copy(android.telephony.mbms.DownloadRequest);
method public int describeContents();
+ method public android.net.Uri getDestinationUri();
method public java.lang.String getFileServiceId();
method public static int getMaxAppIntentSize();
method public static int getMaxDestinationUriSize();
method public android.net.Uri getSourceUri();
method public int getSubscriptionId();
+ method public byte[] toByteArray();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.mbms.DownloadRequest> CREATOR;
}
public static class DownloadRequest.Builder {
- ctor public DownloadRequest.Builder(android.net.Uri);
+ ctor public DownloadRequest.Builder(android.net.Uri, android.net.Uri);
method public android.telephony.mbms.DownloadRequest build();
+ method public static android.telephony.mbms.DownloadRequest.Builder fromDownloadRequest(android.telephony.mbms.DownloadRequest);
+ method public static android.telephony.mbms.DownloadRequest.Builder fromSerializedRequest(byte[]);
method public android.telephony.mbms.DownloadRequest.Builder setAppIntent(android.content.Intent);
method public android.telephony.mbms.DownloadRequest.Builder setServiceInfo(android.telephony.mbms.FileServiceInfo);
method public android.telephony.mbms.DownloadRequest.Builder setSubscriptionId(int);
@@ -42836,10 +42842,10 @@
method public java.util.Date getSessionStartTime();
}
- public class StreamingService {
+ public class StreamingService implements java.lang.AutoCloseable {
+ method public void close();
method public android.telephony.mbms.StreamingServiceInfo getInfo();
method public android.net.Uri getPlaybackUri();
- method public void stopStreaming();
field public static final int BROADCAST_METHOD = 1; // 0x1
field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
field public static final int REASON_END_OF_SESSION = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 930224d..0f88369 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -10,6 +10,7 @@
field public static final java.lang.String ACCESS_MTP = "android.permission.ACCESS_MTP";
field public static final java.lang.String ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS";
field public static final java.lang.String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
+ field public static final java.lang.String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
field public static final java.lang.String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
field public static final java.lang.String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
field public static final java.lang.String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
@@ -179,6 +180,7 @@
field public static final java.lang.String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
field public static final java.lang.String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
field public static final java.lang.String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
+ field public static final java.lang.String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
field public static final java.lang.String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
field public static final java.lang.String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
@@ -467,7 +469,11 @@
method public android.app.backup.RestoreSession beginRestoreSession();
method public void cancelBackups();
method public long getAvailableRestoreToken(java.lang.String);
+ method public android.content.Intent getConfigurationIntent(java.lang.String);
method public java.lang.String getCurrentTransport();
+ method public android.content.Intent getDataManagementIntent(java.lang.String);
+ method public java.lang.String getDataManagementLabel(java.lang.String);
+ method public java.lang.String getDestinationString(java.lang.String);
method public boolean isAppEligibleForBackup(java.lang.String);
method public boolean isBackupEnabled();
method public boolean isBackupServiceActive(android.os.UserHandle);
@@ -1085,6 +1091,7 @@
public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
field public static final int FLAG_REMOVED = 2; // 0x2
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
+ field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
field public int requestRes;
}
@@ -6157,12 +6164,7 @@
package android.telephony.mbms {
- public final class DownloadRequest implements android.os.Parcelable {
- method public byte[] getOpaqueData();
- }
-
public static class DownloadRequest.Builder {
- method public android.telephony.mbms.DownloadRequest.Builder setOpaqueData(byte[]);
method public android.telephony.mbms.DownloadRequest.Builder setServiceId(java.lang.String);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 92251f7..55e7926 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -167,6 +167,17 @@
}
+package android.app.backup {
+
+ public class BackupManager {
+ method public android.content.Intent getConfigurationIntent(java.lang.String);
+ method public android.content.Intent getDataManagementIntent(java.lang.String);
+ method public java.lang.String getDataManagementLabel(java.lang.String);
+ method public java.lang.String getDestinationString(java.lang.String);
+ }
+
+}
+
package android.app.usage {
public class StorageStatsManager {
@@ -216,6 +227,7 @@
}
public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+ field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
}
@@ -1096,6 +1108,11 @@
method public android.graphics.Bitmap getContent();
method public static android.graphics.PointF getMagnifierDefaultSize();
method public android.graphics.Rect getWindowPositionOnScreen();
+ method public void setOnOperationCompleteCallback(android.widget.Magnifier.Callback);
+ }
+
+ public static abstract interface Magnifier.Callback {
+ method public abstract void onOperationComplete();
}
public class NumberPicker extends android.widget.LinearLayout {
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
index 45a0e7b..ab4382a 100644
--- a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
@@ -104,7 +104,8 @@
for (size_t i=0; i<blockHeader.size(); i++) {
if (!table.insertField(&proto, blockHeader[i], blockCounts[i+1])) {
- return BAD_VALUE;
+ fprintf(stderr, "Header %s has bad data %s\n", blockHeader[i].c_str(),
+ blockCounts[i+1].c_str());
}
}
} else return BAD_VALUE;
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index d2d24c8..6bdd9be 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -25,16 +25,7 @@
LOCAL_MODULE := incidentd
-LOCAL_SRC_FILES := \
- src/PrivacyBuffer.cpp \
- src/FdBuffer.cpp \
- src/IncidentService.cpp \
- src/Privacy.cpp \
- src/Reporter.cpp \
- src/Section.cpp \
- src/incidentd_util.cpp \
- src/main.cpp \
- src/report_directory.cpp
+LOCAL_SRC_FILES := $(call all-cpp-files-under, src) \
LOCAL_CFLAGS += \
-Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
@@ -110,19 +101,15 @@
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
-LOCAL_SRC_FILES := \
+LOCAL_SRC_FILES := $(call all-cpp-files-under, tests) \
src/PrivacyBuffer.cpp \
src/FdBuffer.cpp \
src/Privacy.cpp \
src/Reporter.cpp \
src/Section.cpp \
+ src/Throttler.cpp \
src/incidentd_util.cpp \
src/report_directory.cpp \
- tests/section_list.cpp \
- tests/PrivacyBuffer_test.cpp \
- tests/FdBuffer_test.cpp \
- tests/Reporter_test.cpp \
- tests/Section_test.cpp \
LOCAL_STATIC_LIBRARIES := \
libgmock \
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index 883924c8..db60794 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define DEBUG false
#include "Log.h"
#include "FdBuffer.h"
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 9ae6240..28fb38a 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define DEBUG false
#include "Log.h"
#include "IncidentService.h"
@@ -38,9 +39,11 @@
enum { WHAT_RUN_REPORT = 1, WHAT_SEND_BACKLOG_TO_DROPBOX = 2 };
-//#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL * 60 * 5)
#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL)
+#define DEFAULT_BYTES_SIZE_LIMIT (20 * 1024 * 1024) // 20MB
+#define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day
+
// ================================================================================
String16 const DUMP_PERMISSION("android.permission.DUMP");
String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS");
@@ -113,8 +116,12 @@
}
// ================================================================================
-ReportHandler::ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue)
- : mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS), mHandlerLooper(handlerLooper), mQueue(queue) {}
+ReportHandler::ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue,
+ const sp<Throttler>& throttler)
+ : mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS),
+ mHandlerLooper(handlerLooper),
+ mQueue(queue),
+ mThrottler(throttler) {}
ReportHandler::~ReportHandler() {}
@@ -159,10 +166,17 @@
reporter->batch.add(request);
}
+ if (mThrottler->shouldThrottle()) {
+ ALOGW("RunReport got throttled.");
+ return;
+ }
+
// Take the report, which might take a while. More requests might queue
// up while we're doing this, and we'll handle them in their next batch.
// TODO: We should further rate-limit the reports to no more than N per time-period.
- Reporter::run_report_status_t reportStatus = reporter->runReport();
+ size_t reportByteSize = 0;
+ Reporter::run_report_status_t reportStatus = reporter->runReport(&reportByteSize);
+ mThrottler->addReportSize(reportByteSize);
if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) {
unique_lock<mutex> lock(mLock);
schedule_send_backlog_to_dropbox_locked();
@@ -184,8 +198,9 @@
// ================================================================================
IncidentService::IncidentService(const sp<Looper>& handlerLooper)
- : mQueue(new ReportRequestQueue()) {
- mHandler = new ReportHandler(handlerLooper, mQueue);
+ : mQueue(new ReportRequestQueue()),
+ mThrottler(new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS)) {
+ mHandler = new ReportHandler(handlerLooper, mQueue, mThrottler);
}
IncidentService::~IncidentService() {}
@@ -294,6 +309,10 @@
if (!args[0].compare(String8("privacy"))) {
return cmd_privacy(in, out, err, args);
}
+ if (!args[0].compare(String8("throttler"))) {
+ mThrottler->dump(out);
+ return NO_ERROR;
+ }
}
return cmd_help(out);
}
@@ -302,6 +321,9 @@
fprintf(out, "usage: adb shell cmd incident privacy print <section_id>\n");
fprintf(out, "usage: adb shell cmd incident privacy parse <section_id> < proto.txt\n");
fprintf(out, " Prints/parses for the section id.\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: adb shell cmd incident throttler\n");
+ fprintf(out, " Prints the current throttler state\n");
return NO_ERROR;
}
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index 3c66507..0ab34ed 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -26,6 +26,8 @@
#include <deque>
#include <mutex>
+#include "Throttler.h"
+
using namespace android;
using namespace android::base;
using namespace android::binder;
@@ -49,7 +51,8 @@
// ================================================================================
class ReportHandler : public MessageHandler {
public:
- ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue);
+ ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue,
+ const sp<Throttler>& throttler);
virtual ~ReportHandler();
virtual void handleMessage(const Message& message);
@@ -70,6 +73,7 @@
nsecs_t mBacklogDelay;
sp<Looper> mHandlerLooper;
sp<ReportRequestQueue> mQueue;
+ sp<Throttler> mThrottler;
/**
* Runs all of the reports that have been queued.
@@ -109,6 +113,7 @@
private:
sp<ReportRequestQueue> mQueue;
sp<ReportHandler> mHandler;
+ sp<Throttler> mThrottler;
/**
* Commands print out help.
diff --git a/cmds/incidentd/src/Log.h b/cmds/incidentd/src/Log.h
index 46efbd1..22de46d 100644
--- a/cmds/incidentd/src/Log.h
+++ b/cmds/incidentd/src/Log.h
@@ -23,7 +23,6 @@
#pragma once
#define LOG_TAG "incidentd"
-#define DEBUG false
#include <log/log.h>
diff --git a/cmds/incidentd/src/PrivacyBuffer.cpp b/cmds/incidentd/src/PrivacyBuffer.cpp
index e4128f4..ee57f4d 100644
--- a/cmds/incidentd/src/PrivacyBuffer.cpp
+++ b/cmds/incidentd/src/PrivacyBuffer.cpp
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define DEBUG false
#include "Log.h"
#include "PrivacyBuffer.h"
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index c0b5358..12764f8 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define DEBUG false
#include "Log.h"
#include "Reporter.h"
@@ -118,7 +119,7 @@
Reporter::~Reporter() {}
-Reporter::run_report_status_t Reporter::runReport() {
+Reporter::run_report_status_t Reporter::runReport(size_t* reportByteSize) {
status_t err = NO_ERROR;
bool needMainFd = false;
int mainFd = -1;
@@ -185,7 +186,6 @@
int64_t startTime = uptimeMillis();
err = (*section)->Execute(&batch);
int64_t endTime = uptimeMillis();
-
stats->set_success(err == NO_ERROR);
stats->set_exec_duration_ms(endTime - startTime);
if (err != NO_ERROR) {
@@ -193,6 +193,7 @@
(*section)->name.string(), id, strerror(-err));
goto DONE;
}
+ (*reportByteSize) += stats->report_size_bytes();
// Notify listener of starting
for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) {
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index 0f3f221..ba8965e 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -18,8 +18,6 @@
#ifndef REPORTER_H
#define REPORTER_H
-#include "frameworks/base/libs/incident/proto/android/os/metadata.pb.h"
-
#include <android/os/IIncidentReportStatusListener.h>
#include <android/os/IncidentReportArgs.h>
@@ -29,6 +27,9 @@
#include <time.h>
+#include "Throttler.h"
+#include "frameworks/base/libs/incident/proto/android/os/metadata.pb.h"
+
using namespace android;
using namespace android::os;
using namespace std;
@@ -91,7 +92,7 @@
virtual ~Reporter();
// Run the report as described in the batch and args parameters.
- run_report_status_t runReport();
+ run_report_status_t runReport(size_t* reportByteSize);
static run_report_status_t upload_backlog();
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 2e4e980..64eae3a 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define DEBUG false
#include "Log.h"
#include "Section.h"
@@ -244,7 +245,8 @@
}
if (requests->mainFd() >= 0 && !metadataBuf.empty()) {
write_section_header(requests->mainFd(), id, metadataBuf.size());
- if (!WriteFully(requests->mainFd(), (uint8_t const*)metadataBuf.data(), metadataBuf.size())) {
+ if (!WriteFully(requests->mainFd(), (uint8_t const*)metadataBuf.data(),
+ metadataBuf.size())) {
ALOGW("Failed to write metadata to dropbox fd %d", requests->mainFd());
return -1;
}
diff --git a/cmds/incidentd/src/Throttler.cpp b/cmds/incidentd/src/Throttler.cpp
new file mode 100644
index 0000000..1abf267
--- /dev/null
+++ b/cmds/incidentd/src/Throttler.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#define DEBUG false
+#include "Log.h"
+
+#include "Throttler.h"
+
+#include <utils/SystemClock.h>
+
+Throttler::Throttler(size_t limit, int64_t refractoryPeriodMs)
+ : mSizeLimit(limit),
+ mRefractoryPeriodMs(refractoryPeriodMs),
+ mAccumulatedSize(0),
+ mLastRefractoryMs(android::elapsedRealtime()) {}
+
+Throttler::~Throttler() {}
+
+bool Throttler::shouldThrottle() {
+ int64_t now = android::elapsedRealtime();
+ if (now > mRefractoryPeriodMs + mLastRefractoryMs) {
+ mLastRefractoryMs = now;
+ mAccumulatedSize = 0;
+ }
+ return mAccumulatedSize > mSizeLimit;
+}
+
+void Throttler::addReportSize(size_t reportByteSize) {
+ VLOG("The current request took %d bytes to dropbox", (int)reportByteSize);
+ mAccumulatedSize += reportByteSize;
+}
+
+void Throttler::dump(FILE* out) {
+ fprintf(out, "mSizeLimit=%d\n", (int)mSizeLimit);
+ fprintf(out, "mAccumulatedSize=%d\n", (int)mAccumulatedSize);
+ fprintf(out, "mRefractoryPeriodMs=%d\n", (int)mRefractoryPeriodMs);
+ fprintf(out, "mLastRefractoryMs=%d\n", (int)mLastRefractoryMs);
+}
diff --git a/cmds/incidentd/src/Throttler.h b/cmds/incidentd/src/Throttler.h
new file mode 100644
index 0000000..c56f753
--- /dev/null
+++ b/cmds/incidentd/src/Throttler.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 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 THROTTLER_H
+#define THROTTLER_H
+
+#include <utils/RefBase.h>
+
+#include <unistd.h>
+/**
+ * This is a size-based throttler which prevents incidentd to take more data.
+ */
+class Throttler : public virtual android::RefBase {
+public:
+ Throttler(size_t limit, int64_t refractoryPeriodMs);
+ ~Throttler();
+
+ /**
+ * Asserts this before starting taking report.
+ */
+ bool shouldThrottle();
+
+ void addReportSize(size_t reportByteSize);
+
+ void dump(FILE* out);
+
+private:
+ const size_t mSizeLimit;
+ const int64_t mRefractoryPeriodMs;
+
+ size_t mAccumulatedSize;
+ int64_t mLastRefractoryMs;
+};
+
+#endif // THROTTLER_H
diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp
index b71c066..f023ee1 100644
--- a/cmds/incidentd/src/report_directory.cpp
+++ b/cmds/incidentd/src/report_directory.cpp
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define DEBUG false
#include "Log.h"
#include "report_directory.h"
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index 956c8d3..0e5eec6 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -11,6 +11,7 @@
// 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.
+#define DEBUG false
#include "Log.h"
#include "FdBuffer.h"
diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
index 7ea9bbf..c7c69a7 100644
--- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp
+++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
@@ -11,6 +11,7 @@
// 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.
+#define DEBUG false
#include "Log.h"
#include "FdBuffer.h"
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
index bd359ac..955dbac 100644
--- a/cmds/incidentd/tests/Reporter_test.cpp
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -11,6 +11,7 @@
// 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.
+#define DEBUG false
#include "Log.h"
#include "Reporter.h"
@@ -106,6 +107,7 @@
ReportRequestSet requests;
sp<Reporter> reporter;
sp<TestListener> l;
+ size_t size;
};
TEST_F(ReporterTest, IncidentReportArgs) {
@@ -125,7 +127,7 @@
}
TEST_F(ReporterTest, RunReportEmpty) {
- ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport(&size));
EXPECT_EQ(l->startInvoked, 0);
EXPECT_EQ(l->finishInvoked, 0);
EXPECT_TRUE(l->startSections.empty());
@@ -147,7 +149,7 @@
reporter->batch.add(r1);
reporter->batch.add(r2);
- ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport(&size));
string result;
ReadFileToString(tf.path, &result);
@@ -171,7 +173,7 @@
sp<ReportRequest> r = new ReportRequest(args, l, -1);
reporter->batch.add(r);
- ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport(&size));
vector<string> results = InspectFiles();
ASSERT_EQ((int)results.size(), 1);
EXPECT_EQ(results[0],
@@ -188,7 +190,7 @@
sp<ReportRequest> r = new ReportRequest(args, l, -1);
reporter->batch.add(r);
- ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport(&size));
auto metadata = reporter->batch.metadata();
EXPECT_EQ(IncidentMetadata_Destination_EXPLICIT, metadata.dest());
EXPECT_EQ(1, metadata.request_size());
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index a1f4fdc..026bf74 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -11,6 +11,7 @@
// 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.
+#define DEBUG false
#include "Log.h"
#include "Section.h"
diff --git a/cmds/incidentd/tests/Throttler_test.cpp b/cmds/incidentd/tests/Throttler_test.cpp
new file mode 100644
index 0000000..213dcef
--- /dev/null
+++ b/cmds/incidentd/tests/Throttler_test.cpp
@@ -0,0 +1,37 @@
+// Copyright (C) 2018 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.
+#define DEBUG false
+#include "Log.h"
+
+#include "Throttler.h"
+
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+TEST(ThrottlerTest, DataSizeExceeded) {
+ Throttler t(100, 100000);
+ EXPECT_FALSE(t.shouldThrottle());
+ t.addReportSize(200);
+ EXPECT_TRUE(t.shouldThrottle());
+}
+
+TEST(ThrottlerTest, TimeReset) {
+ Throttler t(100, 500);
+ EXPECT_FALSE(t.shouldThrottle());
+ t.addReportSize(200);
+ EXPECT_TRUE(t.shouldThrottle());
+ sleep(1); // sleep for 1 second to make sure throttler resets
+ EXPECT_FALSE(t.shouldThrottle());
+}
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index a5eae15..b566099 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -21,6 +21,7 @@
name: "libstats_proto_host",
srcs: [
"src/atoms.proto",
+ "src/atom_field_options.proto",
],
shared_libs: [
@@ -30,6 +31,9 @@
proto: {
type: "full",
export_proto_headers: true,
+ include_dirs: [
+ "external/protobuf/src",
+ ],
},
export_shared_lib_headers: [
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 740fdc0..244fbce 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -17,9 +17,8 @@
statsd_common_src := \
../../core/java/android/os/IStatsCompanionService.aidl \
../../core/java/android/os/IStatsManager.aidl \
- src/stats_log.proto \
+ src/stats_log_common.proto \
src/statsd_config.proto \
- src/atoms.proto \
src/FieldValue.cpp \
src/stats_log_util.cpp \
src/anomaly/AnomalyMonitor.cpp \
@@ -168,6 +167,9 @@
LOCAL_SRC_FILES := \
$(statsd_common_src) \
+ src/atom_field_options.proto \
+ src/atoms.proto \
+ src/stats_log.proto \
tests/AnomalyMonitor_test.cpp \
tests/anomaly/AnomalyTracker_test.cpp \
tests/ConfigManager_test.cpp \
@@ -202,9 +204,13 @@
$(statsd_common_static_libraries) \
libgmock
-LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries)
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
-LOCAL_PROTOC_OPTIMIZE_TYPE := lite
+LOCAL_PROTOC_FLAGS := \
+ -Iexternal/protobuf/src
+
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
+ libprotobuf-cpp-full
include $(BUILD_NATIVE_TEST)
@@ -217,6 +223,7 @@
LOCAL_SRC_FILES := \
src/stats_log.proto \
+ src/stats_log_common.proto \
src/statsd_config.proto \
src/perfetto/perfetto_config.proto \
src/atoms.proto
@@ -226,6 +233,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
platformprotoslite
+LOCAL_PROTOC_FLAGS := \
+ -Iexternal/protobuf/src
+
include $(BUILD_STATIC_JAVA_LIBRARY)
##############################
@@ -261,8 +271,6 @@
libgtest_prod \
libstatslog
-LOCAL_PROTOC_OPTIMIZE_TYPE := lite
-
LOCAL_MODULE_TAGS := eng tests
include $(BUILD_NATIVE_BENCHMARK)
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index d901bd6..3c69f9e 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -22,6 +22,8 @@
namespace android {
namespace os {
namespace statsd {
+
+using std::string;
using std::vector;
android::hash_t hashDimension(const HashableDimensionKey& value) {
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 791fb14..26db3af 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -659,6 +659,7 @@
"Only system uid can call informOnePackageRemoved");
}
mUidMap->removeApp(app, uid);
+ mConfigManager->RemoveConfigs(uid);
return Status::ok();
}
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 6553a14..babe0fc 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -192,7 +192,7 @@
if (!mSubscriptions.empty()) {
if (mAlert.has_id()) {
- ALOGI("An anomaly (%llu) has occurred! Informing subscribers.", mAlert.id());
+ ALOGI("An anomaly (%lld) has occurred! Informing subscribers.", mAlert.id());
informSubscribers(key);
} else {
ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto
new file mode 100644
index 0000000..19d00b7
--- /dev/null
+++ b/cmds/statsd/src/atom_field_options.proto
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.os.statsd;
+option java_package = "com.android.os";
+option java_multiple_files = true;
+option java_outer_classname = "AtomFieldOptions";
+
+import "google/protobuf/descriptor.proto";
+
+enum StateField {
+ // Default value for fields that are not primary or exclusive state.
+ STATE_FIELD_UNSET = 0;
+ // Fields that represent the key that the state belongs to.
+ PRIMARY = 1;
+ // The field that represents the state. It's an exclusive state.
+ EXCLUSIVE = 2;
+}
+
+// Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE
+// exclusive state field, and any number of primary key fields.
+// For example,
+// message UidProcessStateChanged {
+// optional int32 uid = 1 [(stateFieldOption).option = PRIMARY];
+// optional android.app.ProcessStateEnum state = 2 [(stateFieldOption).option = EXCLUSIVE];
+// }
+// Each of this UidProcessStateChanged atom represents a state change for a specific uid.
+// A new state automatically overrides the previous state.
+//
+// If the atom has 2 or more primary fields, it means the combination of the primary fields are
+// the primary key.
+// For example:
+// message ThreadStateChanged {
+// optional int32 pid = 1 [(stateFieldOption).option = PRIMARY];
+// optional int32 tid = 2 [(stateFieldOption).option = PRIMARY];
+// optional int32 state = 3 [(stateFieldOption).option = EXCLUSIVE];
+// }
+//
+// Sometimes, there is no primary key field, when the state is GLOBAL.
+// For example,
+//
+// message ScreenStateChanged {
+// optional android.view.DisplayStateEnum state = 1 [(stateFieldOption).option = EXCLUSIVE];
+// }
+//
+// Only fields of primary types can be annotated. AttributionNode cannot be primary keys (and they
+// usually are not).
+message StateAtomFieldOption {
+ optional StateField option = 1 [default = STATE_FIELD_UNSET];
+}
+
+extend google.protobuf.FieldOptions {
+ // Flags to decorate an atom that presents a state change.
+ optional StateAtomFieldOption stateFieldOption = 50000;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index d9094dd..2b02025 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -21,6 +21,7 @@
option java_package = "com.android.os";
option java_outer_classname = "AtomsProto";
+import "frameworks/base/cmds/statsd/src/atom_field_options.proto";
import "frameworks/base/core/proto/android/app/enums.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/enums.proto";
@@ -179,7 +180,7 @@
*/
message ScreenStateChanged {
// New screen state, from frameworks/base/core/proto/android/view/enums.proto.
- optional android.view.DisplayStateEnum state = 1;
+ optional android.view.DisplayStateEnum state = 1 [(stateFieldOption).option = EXCLUSIVE];
}
/**
@@ -189,10 +190,10 @@
* frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
*/
message UidProcessStateChanged {
- optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
+ optional int32 uid = 1 [(stateFieldOption).option = PRIMARY];
// The state, from frameworks/base/core/proto/android/app/enums.proto.
- optional android.app.ProcessStateEnum state = 2;
+ optional android.app.ProcessStateEnum state = 2 [(stateFieldOption).option = EXCLUSIVE];
}
/**
@@ -1514,5 +1515,4 @@
*/
message FullBatteryCapacity {
optional int32 capacity_uAh = 1;
-}
-
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 7baa5e5..8c16e4e 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -16,7 +16,7 @@
#pragma once
#include "config/ConfigKey.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/stats_log_common.pb.h"
#include "statslog.h"
#include <gtest/gtest_prod.h>
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index f07fc66..d282b86 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -26,9 +26,10 @@
namespace statsd {
using namespace android::util;
+using android::util::ProtoOutputStream;
using std::ostringstream;
using std::string;
-using android::util::ProtoOutputStream;
+using std::vector;
LogEvent::LogEvent(log_msg& msg) {
mContext =
@@ -130,7 +131,7 @@
return false;
}
-bool LogEvent::write(const std::vector<AttributionNode>& nodes) {
+bool LogEvent::write(const std::vector<AttributionNodeInternal>& nodes) {
if (mContext) {
if (android_log_write_list_begin(mContext) < 0) {
return false;
@@ -148,7 +149,7 @@
return false;
}
-bool LogEvent::write(const AttributionNode& node) {
+bool LogEvent::write(const AttributionNodeInternal& node) {
if (mContext) {
if (android_log_write_list_begin(mContext) < 0) {
return false;
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index b3084d5..24d624d 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -17,7 +17,6 @@
#pragma once
#include "FieldValue.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include <android/util/ProtoOutputStream.h>
#include <log/log_event_list.h>
@@ -32,8 +31,26 @@
namespace os {
namespace statsd {
-using std::string;
-using std::vector;
+struct AttributionNodeInternal {
+ void set_uid(int32_t id) {
+ mUid = id;
+ }
+
+ void set_tag(const std::string& value) {
+ mTag = value;
+ }
+
+ int32_t uid() const {
+ return mUid;
+ }
+
+ const std::string& tag() const {
+ return mTag;
+ }
+
+ int32_t mUid;
+ std::string mTag;
+};
/**
* Wrapper for the log_msg structure.
*/
@@ -89,15 +106,15 @@
bool write(int32_t value);
bool write(uint64_t value);
bool write(int64_t value);
- bool write(const string& value);
+ bool write(const std::string& value);
bool write(float value);
- bool write(const std::vector<AttributionNode>& nodes);
- bool write(const AttributionNode& node);
+ bool write(const std::vector<AttributionNodeInternal>& nodes);
+ bool write(const AttributionNodeInternal& node);
/**
* Return a string representation of this event.
*/
- string ToString() const;
+ std::string ToString() const;
/**
* Write this object to a ProtoOutputStream.
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
index 872cd8e..ae946d1 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -24,10 +24,10 @@
#include <string>
#include <unordered_map>
#include <vector>
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/stats_log_common.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "stats_util.h"
#include "packages/UidMap.h"
+#include "stats_util.h"
namespace android {
namespace os {
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index fddfd93..96d0cfc 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -129,8 +129,6 @@
long long wrapperToken =
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
-
- long long eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOMS);
const bool truncateTimestamp =
android::util::kNotTruncatingTimestampAtomWhiteList.find(event.GetTagId()) ==
android::util::kNotTruncatingTimestampAtomWhiteList.end();
@@ -145,6 +143,8 @@
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_WALL_CLOCK_TIMESTAMP_NANOS,
(long long)getWallClockNs());
}
+
+ long long eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOMS);
event.ToProto(*mProto);
mProto->end(eventToken);
mProto->end(wrapperToken);
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index f1da452..c41e0aa 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -18,7 +18,7 @@
#include "config/ConfigKey.h"
#include "config/ConfigListener.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/stats_log_common.pb.h"
#include "packages/PackageInfoListener.h"
#include <binder/IResultReceiver.h>
diff --git a/cmds/statsd/src/perfetto/perfetto_config.proto b/cmds/statsd/src/perfetto/perfetto_config.proto
index dc868f9..56d12f8 100644
--- a/cmds/statsd/src/perfetto/perfetto_config.proto
+++ b/cmds/statsd/src/perfetto/perfetto_config.proto
@@ -15,7 +15,6 @@
*/
syntax = "proto2";
-option optimize_for = LITE_RUNTIME;
package perfetto.protos;
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index b427485..272e90b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -15,7 +15,6 @@
*/
syntax = "proto2";
-option optimize_for = LITE_RUNTIME;
package android.os.statsd;
@@ -23,6 +22,7 @@
option java_outer_classname = "StatsLog";
import "frameworks/base/cmds/statsd/src/atoms.proto";
+import "frameworks/base/cmds/statsd/src/stats_log_common.proto";
message DimensionsValue {
optional int32 field = 1;
@@ -115,33 +115,6 @@
repeated GaugeBucketInfo bucket_info = 3;
}
-message UidMapping {
- message PackageInfoSnapshot {
- message PackageInfo {
- optional string name = 1;
-
- optional int64 version = 2;
-
- optional int32 uid = 3;
- }
- optional int64 elapsed_timestamp_nanos = 1;
-
- repeated PackageInfo package_info = 2;
- }
- repeated PackageInfoSnapshot snapshots = 1;
-
- message Change {
- optional bool deletion = 1;
-
- optional int64 elapsed_timestamp_nanos = 2;
- optional string app = 3;
- optional int32 uid = 4;
-
- optional int64 version = 5;
- }
- repeated Change changes = 2;
-}
-
message StatsLogReport {
optional int64 metric_id = 1;
@@ -191,87 +164,4 @@
optional ConfigKey config_key = 1;
repeated ConfigMetricsReport reports = 2;
-}
-
-message StatsdStatsReport {
- optional int32 stats_begin_time_sec = 1;
-
- optional int32 stats_end_time_sec = 2;
-
- message MatcherStats {
- optional int64 id = 1;
- optional int32 matched_times = 2;
- }
-
- message ConditionStats {
- optional int64 id = 1;
- optional int32 max_tuple_counts = 2;
- }
-
- message MetricStats {
- optional int64 id = 1;
- optional int32 max_tuple_counts = 2;
- }
-
- message AlertStats {
- optional int64 id = 1;
- optional int32 alerted_times = 2;
- }
-
- message ConfigStats {
- optional int32 uid = 1;
- optional int64 id = 2;
- optional int32 creation_time_sec = 3;
- optional int32 deletion_time_sec = 4;
- optional int32 metric_count = 5;
- optional int32 condition_count = 6;
- optional int32 matcher_count = 7;
- optional int32 alert_count = 8;
- optional bool is_valid = 9;
-
- repeated int32 broadcast_sent_time_sec = 10;
- repeated int32 data_drop_time_sec = 11;
- repeated int32 dump_report_time_sec = 12;
- repeated MatcherStats matcher_stats = 13;
- repeated ConditionStats condition_stats = 14;
- repeated MetricStats metric_stats = 15;
- repeated AlertStats alert_stats = 16;
- }
-
- repeated ConfigStats config_stats = 3;
-
- message AtomStats {
- optional int32 tag = 1;
- optional int32 count = 2;
- }
-
- repeated AtomStats atom_stats = 7;
-
- message UidMapStats {
- optional int32 snapshots = 1;
- optional int32 changes = 2;
- optional int32 bytes_used = 3;
- optional int32 dropped_snapshots = 4;
- optional int32 dropped_changes = 5;
- }
- optional UidMapStats uidmap_stats = 8;
-
- message AnomalyAlarmStats {
- optional int32 alarms_registered = 1;
- }
- optional AnomalyAlarmStats anomaly_alarm_stats = 9;
-
- message PulledAtomStats {
- optional int32 atom_id = 1;
- optional int64 total_pull = 2;
- optional int64 total_pull_from_cache = 3;
- optional int64 min_pull_interval_sec = 4;
- }
- repeated PulledAtomStats pulled_atom_stats = 10;
-
- message LoggerErrorStats {
- optional int32 logger_disconnection_sec = 1;
- optional int32 error_code = 2;
- }
- repeated LoggerErrorStats logger_error_stats = 11;
}
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_log_common.proto b/cmds/statsd/src/stats_log_common.proto
new file mode 100644
index 0000000..aeecd23
--- /dev/null
+++ b/cmds/statsd/src/stats_log_common.proto
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.os.statsd;
+
+option java_package = "com.android.os";
+option java_outer_classname = "StatsLogCommon";
+
+message UidMapping {
+ message PackageInfoSnapshot {
+ message PackageInfo {
+ optional string name = 1;
+
+ optional int64 version = 2;
+
+ optional int32 uid = 3;
+ }
+ optional int64 elapsed_timestamp_nanos = 1;
+
+ repeated PackageInfo package_info = 2;
+ }
+ repeated PackageInfoSnapshot snapshots = 1;
+
+ message Change {
+ optional bool deletion = 1;
+
+ optional int64 elapsed_timestamp_nanos = 2;
+ optional string app = 3;
+ optional int32 uid = 4;
+
+ optional int64 version = 5;
+ }
+ repeated Change changes = 2;
+}
+
+message StatsdStatsReport {
+ optional int32 stats_begin_time_sec = 1;
+
+ optional int32 stats_end_time_sec = 2;
+
+ message MatcherStats {
+ optional int64 id = 1;
+ optional int32 matched_times = 2;
+ }
+
+ message ConditionStats {
+ optional int64 id = 1;
+ optional int32 max_tuple_counts = 2;
+ }
+
+ message MetricStats {
+ optional int64 id = 1;
+ optional int32 max_tuple_counts = 2;
+ }
+
+ message AlertStats {
+ optional int64 id = 1;
+ optional int32 alerted_times = 2;
+ }
+
+ message ConfigStats {
+ optional int32 uid = 1;
+ optional int64 id = 2;
+ optional int32 creation_time_sec = 3;
+ optional int32 deletion_time_sec = 4;
+ optional int32 metric_count = 5;
+ optional int32 condition_count = 6;
+ optional int32 matcher_count = 7;
+ optional int32 alert_count = 8;
+ optional bool is_valid = 9;
+
+ repeated int32 broadcast_sent_time_sec = 10;
+ repeated int32 data_drop_time_sec = 11;
+ repeated int32 dump_report_time_sec = 12;
+ repeated MatcherStats matcher_stats = 13;
+ repeated ConditionStats condition_stats = 14;
+ repeated MetricStats metric_stats = 15;
+ repeated AlertStats alert_stats = 16;
+ }
+
+ repeated ConfigStats config_stats = 3;
+
+ message AtomStats {
+ optional int32 tag = 1;
+ optional int32 count = 2;
+ }
+
+ repeated AtomStats atom_stats = 7;
+
+ message UidMapStats {
+ optional int32 snapshots = 1;
+ optional int32 changes = 2;
+ optional int32 bytes_used = 3;
+ optional int32 dropped_snapshots = 4;
+ optional int32 dropped_changes = 5;
+ }
+ optional UidMapStats uidmap_stats = 8;
+
+ message AnomalyAlarmStats {
+ optional int32 alarms_registered = 1;
+ }
+ optional AnomalyAlarmStats anomaly_alarm_stats = 9;
+
+ message PulledAtomStats {
+ optional int32 atom_id = 1;
+ optional int64 total_pull = 2;
+ optional int64 total_pull_from_cache = 3;
+ optional int64 min_pull_interval_sec = 4;
+ }
+ repeated PulledAtomStats pulled_atom_stats = 10;
+
+ message LoggerErrorStats {
+ optional int32 logger_disconnection_sec = 1;
+ optional int32 error_code = 2;
+ }
+ repeated LoggerErrorStats logger_error_stats = 11;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 5922642..c512e3c 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -19,7 +19,7 @@
#include <android/util/ProtoOutputStream.h>
#include "FieldValue.h"
#include "HashableDimensionKey.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/stats_log_common.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "guardrail/StatsdStats.h"
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 31f51a7..c4b47dc 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -18,7 +18,7 @@
#include <sstream>
#include "HashableDimensionKey.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/stats_log_common.pb.h"
#include "logd/LogReader.h"
#include <unordered_map>
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 5a326a4..8750275 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -15,7 +15,6 @@
*/
syntax = "proto2";
-option optimize_for = LITE_RUNTIME;
package android.os.statsd;
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index 9f68fc4..95ecf80 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -27,6 +27,8 @@
namespace os {
namespace statsd {
+using std::vector;
+
void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey,
int64_t subscriberId,
const sp<IBinder>& intentSender) {
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index c7d1a5b..50100df 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -21,7 +21,6 @@
#include "config/ConfigKey.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // subscription
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" // DimensionsValue
#include "android/os/StatsDimensionsValue.h"
#include "HashableDimensionKey.h"
diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp
index f1ad0c8..5846761 100644
--- a/cmds/statsd/tests/FieldValue_test.cpp
+++ b/cmds/statsd/tests/FieldValue_test.cpp
@@ -14,9 +14,10 @@
* limitations under the License.
*/
#include <gtest/gtest.h>
-#include "src/logd/LogEvent.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "matchers/matcher_util.h"
+#include "src/logd/LogEvent.h"
#include "stats_log_util.h"
#include "stats_util.h"
#include "subscriber/SubscriberReporter.h"
@@ -64,19 +65,19 @@
vector<Matcher> matchers;
translateFieldMatcher(matcher1, &matchers);
- AttributionNode attribution_node1;
+ AttributionNodeInternal attribution_node1;
attribution_node1.set_uid(1111);
attribution_node1.set_tag("location1");
- AttributionNode attribution_node2;
+ AttributionNodeInternal attribution_node2;
attribution_node2.set_uid(2222);
attribution_node2.set_tag("location2");
- AttributionNode attribution_node3;
+ AttributionNodeInternal attribution_node3;
attribution_node3.set_uid(3333);
attribution_node3.set_tag("location3");
- std::vector<AttributionNode> attribution_nodes = {attribution_node1, attribution_node2,
- attribution_node3};
+ std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
+ attribution_node3};
// Set up the event
LogEvent event(10, 12345);
@@ -154,19 +155,19 @@
}
TEST(AtomMatcherTest, TestMetric2ConditionLink) {
- AttributionNode attribution_node1;
+ AttributionNodeInternal attribution_node1;
attribution_node1.set_uid(1111);
attribution_node1.set_tag("location1");
- AttributionNode attribution_node2;
+ AttributionNodeInternal attribution_node2;
attribution_node2.set_uid(2222);
attribution_node2.set_tag("location2");
- AttributionNode attribution_node3;
+ AttributionNodeInternal attribution_node3;
attribution_node3.set_uid(3333);
attribution_node3.set_tag("location3");
- std::vector<AttributionNode> attribution_nodes = {attribution_node1, attribution_node2,
- attribution_node3};
+ std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
+ attribution_node3};
// Set up the event
LogEvent event(10, 12345);
@@ -298,15 +299,15 @@
}
TEST(AtomMatcherTest, TestWriteAtomToProto) {
- AttributionNode attribution_node1;
+ AttributionNodeInternal attribution_node1;
attribution_node1.set_uid(1111);
attribution_node1.set_tag("location1");
- AttributionNode attribution_node2;
+ AttributionNodeInternal attribution_node2;
attribution_node2.set_uid(2222);
attribution_node2.set_tag("location2");
- std::vector<AttributionNode> attribution_nodes = {attribution_node1, attribution_node2};
+ std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2};
// Set up the event
LogEvent event(4, 12345);
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 1023ea4..2320a9d 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -60,19 +60,19 @@
TEST(AtomMatcherTest, TestAttributionMatcher) {
UidMap uidMap;
- AttributionNode attribution_node1;
+ AttributionNodeInternal attribution_node1;
attribution_node1.set_uid(1111);
attribution_node1.set_tag("location1");
- AttributionNode attribution_node2;
+ AttributionNodeInternal attribution_node2;
attribution_node2.set_uid(2222);
attribution_node2.set_tag("location2");
- AttributionNode attribution_node3;
+ AttributionNodeInternal attribution_node3;
attribution_node3.set_uid(3333);
attribution_node3.set_tag("location3");
- std::vector<AttributionNode> attribution_nodes =
- { attribution_node1, attribution_node2, attribution_node3 };
+ std::vector<AttributionNodeInternal> attribution_nodes = {attribution_node1, attribution_node2,
+ attribution_node3};
// Set up the event
LogEvent event(TAG_ID, 0);
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index b649215..2fcde29 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -25,14 +25,14 @@
TEST(LogEventTest, TestLogParsing) {
LogEvent event1(1, 2000);
- std::vector<AttributionNode> nodes;
+ std::vector<AttributionNodeInternal> nodes;
- AttributionNode node1;
+ AttributionNodeInternal node1;
node1.set_uid(1000);
node1.set_tag("tag1");
nodes.push_back(node1);
- AttributionNode node2;
+ AttributionNodeInternal node2;
node2.set_uid(2000);
node2.set_tag("tag2");
nodes.push_back(node2);
@@ -92,17 +92,17 @@
TEST(LogEventTest, TestLogParsing2) {
LogEvent event1(1, 2000);
- std::vector<AttributionNode> nodes;
+ std::vector<AttributionNodeInternal> nodes;
event1.write("hello");
// repeated msg can be in the middle
- AttributionNode node1;
+ AttributionNodeInternal node1;
node1.set_uid(1000);
node1.set_tag("tag1");
nodes.push_back(node1);
- AttributionNode node2;
+ AttributionNodeInternal node2;
node2.set_uid(2000);
node2.set_tag("tag2");
nodes.push_back(node2);
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 038d449..3dc3fd1 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -58,9 +58,9 @@
}
void writeAttributionNodesToEvent(LogEvent* event, const std::vector<int> &uids) {
- std::vector<AttributionNode> nodes;
+ std::vector<AttributionNodeInternal> nodes;
for (size_t i = 0; i < uids.size(); ++i) {
- AttributionNode node;
+ AttributionNodeInternal node;
node.set_uid(uids[i]);
nodes.push_back(node);
}
diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
index 0228004..7a7e000 100644
--- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
@@ -75,41 +75,40 @@
android::String16("APP3"), 333 /* uid */, 2 /* version code*/);
// GMS core node is in the middle.
- std::vector<AttributionNode> attributions1 =
- {CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(333, "App3")};
+ std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(333, "App3")};
// GMS core node is the last one.
- std::vector<AttributionNode> attributions2 =
- {CreateAttribution(111, "App1"), CreateAttribution(333, "App3"),
- CreateAttribution(222, "GMSCoreModule1")};
+ std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App1"),
+ CreateAttribution(333, "App3"),
+ CreateAttribution(222, "GMSCoreModule1")};
// GMS core node is the first one.
- std::vector<AttributionNode> attributions3 =
- {CreateAttribution(222, "GMSCoreModule1"), CreateAttribution(333, "App3")};
+ std::vector<AttributionNodeInternal> attributions3 = {CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(333, "App3")};
// Single GMS core node.
- std::vector<AttributionNode> attributions4 =
- {CreateAttribution(222, "GMSCoreModule1")};
+ std::vector<AttributionNodeInternal> attributions4 = {CreateAttribution(222, "GMSCoreModule1")};
// GMS core has another uid.
- std::vector<AttributionNode> attributions5 =
- {CreateAttribution(111, "App1"), CreateAttribution(444, "GMSCoreModule2"),
- CreateAttribution(333, "App3")};
+ std::vector<AttributionNodeInternal> attributions5 = {CreateAttribution(111, "App1"),
+ CreateAttribution(444, "GMSCoreModule2"),
+ CreateAttribution(333, "App3")};
// Multiple GMS core nodes.
- std::vector<AttributionNode> attributions6 =
- {CreateAttribution(444, "GMSCoreModule2"), CreateAttribution(222, "GMSCoreModule1")};
+ std::vector<AttributionNodeInternal> attributions6 = {CreateAttribution(444, "GMSCoreModule2"),
+ CreateAttribution(222, "GMSCoreModule1")};
// No GMS core nodes.
- std::vector<AttributionNode> attributions7 =
- {CreateAttribution(111, "App1"), CreateAttribution(333, "App3")};
- std::vector<AttributionNode> attributions8 = {CreateAttribution(111, "App1")};
+ std::vector<AttributionNodeInternal> attributions7 = {CreateAttribution(111, "App1"),
+ CreateAttribution(333, "App3")};
+ std::vector<AttributionNodeInternal> attributions8 = {CreateAttribution(111, "App1")};
// GMS core node with isolated uid.
const int isolatedUid = 666;
- std::vector<AttributionNode> attributions9 =
- {CreateAttribution(isolatedUid, "GMSCoreModule3")};
+ std::vector<AttributionNodeInternal> attributions9 = {
+ CreateAttribution(isolatedUid, "GMSCoreModule3")};
std::vector<std::unique_ptr<LogEvent>> events;
// Events 1~4 are in the 1st bucket.
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
index 4dffd13..01348bd 100644
--- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
@@ -78,13 +78,13 @@
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
- std::vector<AttributionNode> attributions1 = {CreateAttribution(111, "App1"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+ std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
- std::vector<AttributionNode> attributions2 = {CreateAttribution(333, "App2"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(555, "GMSCoreModule2")};
+ std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(333, "App2"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
std::vector<std::unique_ptr<LogEvent>> events;
events.push_back(
@@ -284,13 +284,13 @@
auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
- std::vector<AttributionNode> attributions1 = {CreateAttribution(111, "App1"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+ std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
- std::vector<AttributionNode> attributions2 = {CreateAttribution(333, "App2"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(555, "GMSCoreModule2")};
+ std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(333, "App2"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
std::vector<std::unique_ptr<LogEvent>> events;
@@ -464,13 +464,13 @@
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
- std::vector<AttributionNode> attributions1 = {CreateAttribution(111, "App1"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
- std::vector<AttributionNode> attributions2 = {CreateAttribution(333, "App2"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(555, "GMSCoreModule2")};
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
std::vector<std::unique_ptr<LogEvent>> events;
@@ -629,13 +629,13 @@
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
- std::vector<AttributionNode> attributions1 = {CreateAttribution(111, "App1"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
- std::vector<AttributionNode> attributions2 = {CreateAttribution(333, "App2"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(555, "GMSCoreModule2")};
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
std::vector<std::unique_ptr<LogEvent>> events;
diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
index 1b51780..c874d92 100644
--- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
@@ -134,8 +134,8 @@
CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
bucketStartTimeNs + 2 * bucketSizeNs - 100);
- std::vector<AttributionNode> attributions =
- {CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")};
+ std::vector<AttributionNodeInternal> attributions = {
+ CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")};
auto syncOnEvent1 =
CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50);
auto syncOffEvent1 =
@@ -249,8 +249,8 @@
CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
bucketStartTimeNs + 2 * bucketSizeNs - 100);
- std::vector<AttributionNode> attributions = {CreateAttribution(appUid, "App1"),
- CreateAttribution(appUid + 1, "GMSCoreModule1")};
+ std::vector<AttributionNodeInternal> attributions = {
+ CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")};
auto syncOnEvent1 = CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50);
auto syncOffEvent1 =
CreateSyncEndEvent(attributions, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300);
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
index efdab98..9153795 100644
--- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -60,13 +60,13 @@
return config;
}
-std::vector<AttributionNode> attributions1 = {CreateAttribution(111, "App1"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
-std::vector<AttributionNode> attributions2 = {CreateAttribution(111, "App2"),
- CreateAttribution(222, "GMSCoreModule1"),
- CreateAttribution(222, "GMSCoreModule2")};
+std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App2"),
+ CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
/*
Events:
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index b7acef7..7568348 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -291,8 +291,8 @@
}
std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(
- const std::vector<AttributionNode>& attributions, const string& wakelockName,
- const WakelockStateChanged::State state, uint64_t timestampNs) {
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ const WakelockStateChanged::State state, uint64_t timestampNs) {
auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs);
event->write(attributions);
event->write(android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
@@ -303,15 +303,15 @@
}
std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
- const std::vector<AttributionNode>& attributions,
- const string& wakelockName, uint64_t timestampNs) {
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ uint64_t timestampNs) {
return CreateWakelockStateChangedEvent(
attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs);
}
std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
- const std::vector<AttributionNode>& attributions,
- const string& wakelockName, uint64_t timestampNs) {
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ uint64_t timestampNs) {
return CreateWakelockStateChangedEvent(
attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs);
}
@@ -339,8 +339,8 @@
}
std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(
- const std::vector<AttributionNode>& attributions,
- const string& name, const SyncStateChanged::State state, uint64_t timestampNs) {
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ const SyncStateChanged::State state, uint64_t timestampNs) {
auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs);
event->write(attributions);
event->write(name);
@@ -350,12 +350,14 @@
}
std::unique_ptr<LogEvent> CreateSyncStartEvent(
- const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs){
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ uint64_t timestampNs) {
return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs);
}
std::unique_ptr<LogEvent> CreateSyncEndEvent(
- const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs) {
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ uint64_t timestampNs) {
return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs);
}
@@ -396,8 +398,8 @@
return processor;
}
-AttributionNode CreateAttribution(const int& uid, const string& tag) {
- AttributionNode attribution;
+AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) {
+ AttributionNodeInternal attribution;
attribution.set_uid(uid);
attribution.set_tag(tag);
return attribution;
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 5d83ed7..1708cc3 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -15,10 +15,11 @@
#pragma once
#include <gtest/gtest.h>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "statslog.h"
-#include "src/logd/LogEvent.h"
#include "src/StatsLogProcessor.h"
+#include "src/logd/LogEvent.h"
+#include "statslog.h"
namespace android {
namespace os {
@@ -119,11 +120,13 @@
// Create log event when the app sync starts.
std::unique_ptr<LogEvent> CreateSyncStartEvent(
- const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs);
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ uint64_t timestampNs);
// Create log event when the app sync ends.
std::unique_ptr<LogEvent> CreateSyncEndEvent(
- const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs);
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ uint64_t timestampNs);
// Create log event when the app sync ends.
std::unique_ptr<LogEvent> CreateAppCrashEvent(
@@ -131,20 +134,20 @@
// Create log event for acquiring wakelock.
std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
- const std::vector<AttributionNode>& attributions,
- const string& wakelockName, uint64_t timestampNs);
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ uint64_t timestampNs);
// Create log event for releasing wakelock.
std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
- const std::vector<AttributionNode>& attributions,
- const string& wakelockName, uint64_t timestampNs);
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ uint64_t timestampNs);
// Create log event for releasing wakelock.
std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(
int isolatedUid, int hostUid, bool is_create, uint64_t timestampNs);
-// Helper function to create an AttributionNode proto.
-AttributionNode CreateAttribution(const int& uid, const string& tag);
+// Helper function to create an AttributionNodeInternal proto.
+AttributionNodeInternal CreateAttribution(const int& uid, const string& tag);
// Create a statsd log event processor upon the start time in seconds, config and key.
sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
diff --git a/cmds/statsd/tools/Android.mk b/cmds/statsd/tools/Android.mk
index faf2d2c..7253c96 100644
--- a/cmds/statsd/tools/Android.mk
+++ b/cmds/statsd/tools/Android.mk
@@ -16,5 +16,5 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-# Include the sub-makefiles
+#Include the sub-makefiles
include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk
index a65095f..c7e4c7b 100644
--- a/cmds/statsd/tools/dogfood/Android.mk
+++ b/cmds/statsd/tools/dogfood/Android.mk
@@ -24,7 +24,6 @@
LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite \
statsdprotolite
-LOCAL_PROTOC_OPTIMIZE_TYPE := lite
LOCAL_PRIVILEGED_MODULE := true
LOCAL_DEX_PREOPT := false
LOCAL_CERTIFICATE := platform
diff --git a/cmds/statsd/tools/loadtest/Android.mk b/cmds/statsd/tools/loadtest/Android.mk
index f5722c2..091f184 100644
--- a/cmds/statsd/tools/loadtest/Android.mk
+++ b/cmds/statsd/tools/loadtest/Android.mk
@@ -19,15 +19,11 @@
LOCAL_PACKAGE_NAME := StatsdLoadtest
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SRC_FILES += ../../src/stats_log.proto \
- ../../src/atoms.proto \
- ../../src/perfetto/perfetto_config.proto \
- ../../src/statsd_config.proto
-LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite
-LOCAL_PROTOC_OPTIMIZE_TYPE := lite
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite \
+ statsdprotolite
+
LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
LOCAL_DEX_PREOPT := false
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
index bed4d98..a12def0 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
@@ -50,7 +50,7 @@
import com.android.os.StatsLog.ConfigMetricsReport;
import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.StatsdStatsReport;
+import com.android.os.StatsLogCommon.StatsdStatsReport;
import com.android.internal.os.StatsdConfigProto.TimeUnit;
import java.util.ArrayList;
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java
index e63150f..58cbcd8 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/StatsdStatsRecorder.java
@@ -16,11 +16,8 @@
package com.android.statsd.loadtest;
import android.content.Context;
-import android.util.Log;
-import com.android.os.StatsLog.StatsdStatsReport;
+import com.android.os.StatsLogCommon.StatsdStatsReport;
import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
public class StatsdStatsRecorder extends PerfDataRecorder {
private static final String TAG = "loadtest.StatsdStatsRecorder";
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index f3997a4..dac03cd 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -1822,7 +1822,7 @@
Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;->selectionModified(IILandroid/view/textclassifier/TextSelection;)Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;
Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;->selectionStarted(I)Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;
Landroid/view/textservice/TextServicesManager;->isSpellCheckerEnabled()Z
-Landroid/view/TextureView;->mLayer:Landroid/view/HardwareLayer;
+Landroid/view/TextureView;->mLayer:Landroid/view/TextureLayer;
Landroid/view/TextureView;->mNativeWindow:J
Landroid/view/TextureView;->mSurface:Landroid/graphics/SurfaceTexture;
Landroid/view/TextureView;->mUpdateListener:Landroid/graphics/SurfaceTexture$OnFrameAvailableListener;
@@ -2403,11 +2403,45 @@
Lcom/android/okhttp/OkHttpClient;->setProtocols(Ljava/util/List;)Lcom/android/okhttp/OkHttpClient;
Lcom/android/okhttp/okio/ByteString;->readObject(Ljava/io/ObjectInputStream;)V
Lcom/android/okhttp/okio/ByteString;->writeObject(Ljava/io/ObjectOutputStream;)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getAlpnSelectedProtocol()[B
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getApplicationProtocol()Ljava/lang/String;
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getApplicationProtocols()[Ljava/lang/String;
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getChannelId()[B
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getHandshakeApplicationProtocol()Ljava/lang/String;
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getHostname()Ljava/lang/String;
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getHostnameOrIP()Ljava/lang/String;
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getNpnSelectedProtocol()[B
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getSoWriteTimeout()I
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setAlpnProtocols([Ljava/lang/String;)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setAlpnProtocols([B)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setApplicationProtocols([Ljava/lang/String;)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setChannelIdEnabled(Z)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setChannelIdPrivateKey(Ljava/security/PrivateKey;)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setHandshakeTimeout(I)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setHostname(Ljava/lang/String;)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setNpnProtocols([B)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setSoWriteTimeout(I)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setUseSessionTickets(Z)V
+Lcom/android/org/conscrypt/ConscryptSocketBase;->getHostname()Ljava/lang/String;
+Lcom/android/org/conscrypt/ConscryptSocketBase;->getHostnameOrIP()Ljava/lang/String;
+Lcom/android/org/conscrypt/ConscryptSocketBase;->getSoWriteTimeout()I
+Lcom/android/org/conscrypt/ConscryptSocketBase;->setHandshakeTimeout(I)V
+Lcom/android/org/conscrypt/ConscryptSocketBase;->setHostname(Ljava/lang/String;)V
+Lcom/android/org/conscrypt/ConscryptSocketBase;->setSoWriteTimeout(I)V
Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getAlpnSelectedProtocol()[B
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getChannelId()[B
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getHostname()Ljava/lang/String;
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getHostnameOrIP()Ljava/lang/String;
Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getNpnSelectedProtocol()[B
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getSoWriteTimeout()I
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setAlpnProtocols([Ljava/lang/String;)V
Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setAlpnProtocols([B)V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setChannelIdEnabled(Z)V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setChannelIdPrivateKey(Ljava/security/PrivateKey;)V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setHandshakeTimeout(I)V
Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setHostname(Ljava/lang/String;)V
Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setNpnProtocols([B)V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setSoWriteTimeout(I)V
Lcom/android/org/conscrypt/OpenSSLSocketImpl;->setUseSessionTickets(Z)V
Lcom/android/org/conscrypt/TrustManagerImpl;-><init>(Ljava/security/KeyStore;)V
Ldalvik/system/BaseDexClassLoader;->getLdLibraryPath()Ljava/lang/String;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 784c3f8..95c2b2c 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -2289,7 +2289,7 @@
android.view.Gravity
android.view.HandlerActionQueue
android.view.HandlerActionQueue$HandlerAction
-android.view.HardwareLayer
+android.view.TextureLayer
android.view.IGraphicsStats
android.view.IGraphicsStats$Stub
android.view.IGraphicsStats$Stub$Proxy
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0c98267..feeb0f2 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -154,8 +154,8 @@
* Callback for window manager to let activity manager know that we are finally starting the
* app transition;
*
- * @param reasons A map from stack id to a reason integer why the transition was started,, which
- * must be one of the APP_TRANSITION_* values.
+ * @param reasons A map from windowing mode to a reason integer why the transition was started,
+ * which must be one of the APP_TRANSITION_* values.
* @param timestamp The time at which the app transition started in
* {@link SystemClock#uptimeMillis()} timebase.
*/
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index d5430f0..09dcbf2 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -164,7 +164,7 @@
/**
* Whether the activity should be launched into LockTask mode.
- * @see #setLockTaskMode(boolean)
+ * @see #setLockTaskEnabled(boolean)
*/
private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
@@ -1148,7 +1148,7 @@
* @see Activity#startLockTask()
* @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
*/
- public ActivityOptions setLockTaskMode(boolean lockTaskMode) {
+ public ActivityOptions setLockTaskEnabled(boolean lockTaskMode) {
mLockTaskMode = lockTaskMode;
return this;
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 13117bc..f8f50a2 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2802,4 +2802,13 @@
return mArtManager;
}
}
+
+ @Override
+ public String getSystemTextClassifierPackageName() {
+ try {
+ return mPM.getSystemTextClassifierPackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 28ecd98..8361711 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4587,7 +4587,8 @@
big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
big.setTextViewText(R.id.notification_material_reply_text_3, null);
- big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
+ big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
+ R.dimen.notification_content_margin);
}
private RemoteViews applyStandardTemplateWithActions(int layoutId) {
@@ -4608,16 +4609,7 @@
if (N > 0) {
big.setViewVisibility(R.id.actions_container, View.VISIBLE);
big.setViewVisibility(R.id.actions, View.VISIBLE);
- if (p.ambient) {
- big.setInt(R.id.actions, "setBackgroundColor", Color.TRANSPARENT);
- } else if (isColorized()) {
- big.setInt(R.id.actions, "setBackgroundColor", getActionBarColor());
- } else {
- big.setInt(R.id.actions, "setBackgroundColor", mContext.getColor(
- R.color.notification_action_list));
- }
- big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
- R.dimen.notification_action_list_height);
+ big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
for (int i=0; i<N; i++) {
Action action = mActions.get(i);
@@ -5226,6 +5218,7 @@
if (mStyle != null) {
mStyle.reduceImageSizes(mContext);
mStyle.purgeResources();
+ mStyle.validate(mContext);
mStyle.buildStyled(mN);
}
mN.reduceImageSizes(mContext);
@@ -5795,6 +5788,13 @@
*/
public void reduceImageSizes(Context context) {
}
+
+ /**
+ * Validate that this style was properly composed. This is called at build time.
+ * @hide
+ */
+ public void validate(Context context) {
+ }
}
/**
@@ -6185,12 +6185,23 @@
* @param user Required - The person displayed for any messages that are sent by the
* user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
* who don't have a Person associated with it will be displayed as if they were sent
- * by this user. The user also needs to have a valid name associated with it.
+ * by this user. The user also needs to have a valid name associated with it, which will
+ * be enforced starting in Android P.
*/
public MessagingStyle(@NonNull Person user) {
mUser = user;
- if (user == null || user.getName() == null) {
- throw new RuntimeException("user must be valid and have a name");
+ }
+
+ /**
+ * Validate that this style was properly composed. This is called at build time.
+ * @hide
+ */
+ @Override
+ public void validate(Context context) {
+ super.validate(context);
+ if (context.getApplicationInfo().targetSdkVersion
+ >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
+ throw new RuntimeException("User must be valid and have a name.");
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 14b2119..9d4d943 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -25,6 +25,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -58,6 +59,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract.Directory;
+import android.provider.Settings;
import android.security.AttestedKeyPair;
import android.security.Credentials;
import android.security.KeyChain;
@@ -1644,11 +1646,15 @@
public static final int LOCK_TASK_FEATURE_HOME = 1 << 2;
/**
- * Enable the Recents button and the Recents screen during LockTask mode.
+ * Enable the Overview button and the Overview screen during LockTask mode. This feature flag
+ * can only be used in combination with {@link #LOCK_TASK_FEATURE_HOME}, and
+ * {@link #setLockTaskFeatures(ComponentName, int)} will throw an
+ * {@link IllegalArgumentException} if this feature flag is defined without
+ * {@link #LOCK_TASK_FEATURE_HOME}.
*
* @see #setLockTaskFeatures(ComponentName, int)
*/
- public static final int LOCK_TASK_FEATURE_RECENTS = 1 << 3;
+ public static final int LOCK_TASK_FEATURE_OVERVIEW = 1 << 3;
/**
* Enable the global actions dialog during LockTask mode. This is the dialog that shows up when
@@ -1680,7 +1686,7 @@
LOCK_TASK_FEATURE_SYSTEM_INFO,
LOCK_TASK_FEATURE_NOTIFICATIONS,
LOCK_TASK_FEATURE_HOME,
- LOCK_TASK_FEATURE_RECENTS,
+ LOCK_TASK_FEATURE_OVERVIEW,
LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
LOCK_TASK_FEATURE_KEYGUARD
})
@@ -2852,8 +2858,8 @@
* When called by a profile owner of a managed profile returns true if the profile uses unified
* challenge with its parent user.
*
- * <strong>Note: This method is not concerned with password quality and will return false if
- * the profile has empty password as a separate challenge.
+ * <strong>Note</strong>: This method is not concerned with password quality and will return
+ * false if the profile has empty password as a separate challenge.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @throws SecurityException if {@code admin} is not a profile owner of a managed profile.
@@ -7191,7 +7197,7 @@
* {@link #LOCK_TASK_FEATURE_SYSTEM_INFO},
* {@link #LOCK_TASK_FEATURE_NOTIFICATIONS},
* {@link #LOCK_TASK_FEATURE_HOME},
- * {@link #LOCK_TASK_FEATURE_RECENTS},
+ * {@link #LOCK_TASK_FEATURE_OVERVIEW},
* {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
* {@link #LOCK_TASK_FEATURE_KEYGUARD}
* @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
@@ -7281,6 +7287,15 @@
}
}
+ /** @hide */
+ @StringDef({
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS,
+ Settings.System.SCREEN_OFF_TIMEOUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SystemSettingsWhitelist {}
+
/**
* Called by device owner to update {@link android.provider.Settings.System} settings.
* Validation that the value of the setting is in the correct form for the setting type should
@@ -7300,8 +7315,8 @@
* @param value The value to update the setting to.
* @throws SecurityException if {@code admin} is not a device owner.
*/
- public void setSystemSetting(@NonNull ComponentName admin, @NonNull String setting,
- String value) {
+ public void setSystemSetting(@NonNull ComponentName admin,
+ @NonNull @SystemSettingsWhitelist String setting, String value) {
throwIfParentInstance("setSystemSetting");
if (mService != null) {
try {
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index faaa004..38b4f8f 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -295,7 +295,7 @@
* <li> [1] admin user ID ({@code Integer})
* <li> [2] target user ID ({@code Integer})
* <li> [3] new maximum number of failed password attempts ({@code Integer})
- * @see DevicePolicyManager#setMaximumTimeToLock(ComponentName, long)
+ * @see DevicePolicyManager#setMaximumFailedPasswordsForWipe(ComponentName, int)
*/
public static final int TAG_MAX_PASSWORD_ATTEMPTS_SET =
SecurityLogTags.SECURITY_MAX_PASSWORD_ATTEMPTS_SET;
@@ -370,7 +370,7 @@
SecurityLogTags.SECURITY_CERT_AUTHORITY_INSTALLED;
/**
- * Indicates that a new oot certificate has been removed from system's trusted credential
+ * Indicates that a new root certificate has been removed from system's trusted credential
* storage. The log entry contains the following information about the event, encapsulated in an
* {@link Object} array and accessible via {@link SecurityEvent#getData()}:
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 6ec0969..debc32b 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -715,6 +716,92 @@
}
}
+ /**
+ * Returns an {@link Intent} for the specified transport's configuration UI.
+ * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String,
+ * Intent, String)}.
+ * @param transportName The name of the registered transport.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public Intent getConfigurationIntent(String transportName) {
+ if (sService != null) {
+ try {
+ return sService.getConfigurationIntent(transportName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getConfigurationIntent() couldn't connect");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a {@link String} describing where the specified transport is sending data.
+ * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String,
+ * Intent, String)}.
+ * @param transportName The name of the registered transport.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public String getDestinationString(String transportName) {
+ if (sService != null) {
+ try {
+ return sService.getDestinationString(transportName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getDestinationString() couldn't connect");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns an {@link Intent} for the specified transport's data management UI.
+ * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String,
+ * Intent, String)}.
+ * @param transportName The name of the registered transport.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public Intent getDataManagementIntent(String transportName) {
+ if (sService != null) {
+ try {
+ return sService.getDataManagementIntent(transportName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getDataManagementIntent() couldn't connect");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a {@link String} describing what the specified transport's data management intent is
+ * used for.
+ * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String,
+ * Intent, String)}.
+ *
+ * @param transportName The name of the registered transport.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public String getDataManagementLabel(String transportName) {
+ if (sService != null) {
+ try {
+ return sService.getDataManagementLabel(transportName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getDataManagementLabel() couldn't connect");
+ }
+ }
+ return null;
+ }
+
/*
* We wrap incoming binder calls with a private class implementation that
* redirects them into main-thread actions. This serializes the backup
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1adc9f5..b2c9edd4 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -37,6 +37,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.ArrayUtils;
+import com.android.server.SystemConfig;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1384,6 +1385,8 @@
classLoaderName = orig.classLoaderName;
splitClassLoaderNames = orig.splitClassLoaderNames;
appComponentFactory = orig.appComponentFactory;
+ compileSdkVersion = orig.compileSdkVersion;
+ compileSdkVersionCodename = orig.compileSdkVersionCodename;
}
public String toString() {
@@ -1601,7 +1604,10 @@
* @hide
*/
public boolean isAllowedToUseHiddenApi() {
- return isSystemApp() || isUpdatedSystemApp();
+ boolean whitelisted =
+ SystemConfig.getInstance().getHiddenApiWhitelistedApps().contains(packageName);
+ return isSystemApp() || // TODO get rid of this once the whitelist has been populated
+ (whitelisted && (isSystemApp() || isUpdatedSystemApp()));
}
/**
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 379bff4..36a74a4 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -660,4 +660,6 @@
boolean hasSigningCertificate(String packageName, in byte[] signingCertificate, int flags);
boolean hasUidSigningCertificate(int uid, in byte[] signingCertificate, int flags);
+
+ String getSystemTextClassifierPackageName();
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d0be6c8..25af1a7 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -448,11 +448,17 @@
/**
* Uninstall the given package, removing it completely from the device. This
- * method is only available to the current "installer of record" for the
- * package.
+ * method is available to:
+ * <ul>
+ * <li>the current "installer of record" for the package
+ * <li>the device owner
+ * <li>the affiliated profile owner
+ * </ul>
*
* @param packageName The package to uninstall.
* @param statusReceiver Where to deliver the result.
+ *
+ * @see android.app.admin.DevicePolicyManager
*/
@RequiresPermission(anyOf = {
Manifest.permission.DELETE_PACKAGES,
@@ -480,14 +486,22 @@
/**
* Uninstall the given package with a specific version code, removing it
- * completely from the device. This method is only available to the current
- * "installer of record" for the package. If the version code of the package
+ * completely from the device. If the version code of the package
* does not match the one passed in the versioned package argument this
* method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to
* uninstall the latest version of the package.
+ * <p>
+ * This method is available to:
+ * <ul>
+ * <li>the current "installer of record" for the package
+ * <li>the device owner
+ * <li>the affiliated profile owner
+ * </ul>
*
* @param versionedPackage The versioned package to uninstall.
* @param statusReceiver Where to deliver the result.
+ *
+ * @see android.app.admin.DevicePolicyManager
*/
@RequiresPermission(anyOf = {
Manifest.permission.DELETE_PACKAGES,
@@ -941,9 +955,14 @@
* Once this method is called, the session is sealed and no additional
* mutations may be performed on the session. If the device reboots
* before the session has been finalized, you may commit the session again.
+ * <p>
+ * If the installer is the device owner or the affiliated profile owner, there will be no
+ * user intervention.
*
* @throws SecurityException if streams opened through
* {@link #openWrite(String, long, long)} are still open.
+ *
+ * @see android.app.admin.DevicePolicyManager
*/
public void commit(@NonNull IntentSender statusReceiver) {
try {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 07a9911..bd7961f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -6022,4 +6022,14 @@
throw new UnsupportedOperationException(
"hasSigningCertificate not implemented in subclass");
}
+
+ /**
+ * @return the system defined text classifier package name, or null if there's none.
+ *
+ * @hide
+ */
+ public String getSystemTextClassifierPackageName() {
+ throw new UnsupportedOperationException(
+ "getSystemTextClassifierPackageName not implemented in subclass");
+ }
}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 6f093ba..41aa9c3 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -43,12 +43,14 @@
public static final int PACKAGE_INSTALLER = 2;
public static final int PACKAGE_VERIFIER = 3;
public static final int PACKAGE_BROWSER = 4;
+ public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5;
@IntDef(value = {
PACKAGE_SYSTEM,
PACKAGE_SETUP_WIZARD,
PACKAGE_INSTALLER,
PACKAGE_VERIFIER,
PACKAGE_BROWSER,
+ PACKAGE_SYSTEM_TEXT_CLASSIFIER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KnownPackage {}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9e4166e..2420b63 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3185,7 +3185,7 @@
perm.info.protectionLevel = PermissionInfo.fixProtectionLevel(perm.info.protectionLevel);
- if ((perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_FLAGS) != 0) {
+ if (perm.info.getProtectionFlags() != 0) {
if ( (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_INSTANT) == 0
&& (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0
&& (perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) !=
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 21bd7f0..938409a 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -16,12 +16,16 @@
package android.content.pm;
+import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Information you can retrieve about a particular security permission
* known to the system. This corresponds to information collected from the
@@ -56,6 +60,16 @@
@Deprecated
public static final int PROTECTION_SIGNATURE_OR_SYSTEM = 3;
+ /** @hide */
+ @IntDef(flag = false, prefix = { "PROTECTION_" }, value = {
+ PROTECTION_NORMAL,
+ PROTECTION_DANGEROUS,
+ PROTECTION_SIGNATURE,
+ PROTECTION_SIGNATURE_OR_SYSTEM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Protection {}
+
/**
* Additional flag for {@link #protectionLevel}, corresponding
* to the <code>privileged</code> value of
@@ -155,30 +169,71 @@
public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000;
/**
- * Mask for {@link #protectionLevel}: the basic protection type.
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>text_classifier</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
*/
+ @SystemApi
+ @TestApi
+ public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 0x10000;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
+ PROTECTION_FLAG_PRIVILEGED,
+ PROTECTION_FLAG_SYSTEM,
+ PROTECTION_FLAG_DEVELOPMENT,
+ PROTECTION_FLAG_APPOP,
+ PROTECTION_FLAG_PRE23,
+ PROTECTION_FLAG_INSTALLER,
+ PROTECTION_FLAG_VERIFIER,
+ PROTECTION_FLAG_PREINSTALLED,
+ PROTECTION_FLAG_SETUP,
+ PROTECTION_FLAG_INSTANT,
+ PROTECTION_FLAG_RUNTIME_ONLY,
+ PROTECTION_FLAG_OEM,
+ PROTECTION_FLAG_VENDOR_PRIVILEGED,
+ PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProtectionFlags {}
+
+ /**
+ * Mask for {@link #protectionLevel}: the basic protection type.
+ *
+ * @deprecated Use #getProtection() instead.
+ */
+ @Deprecated
public static final int PROTECTION_MASK_BASE = 0xf;
/**
* Mask for {@link #protectionLevel}: additional flag bits.
+ *
+ * @deprecated Use #getProtectionFlags() instead.
*/
+ @Deprecated
public static final int PROTECTION_MASK_FLAGS = 0xfff0;
/**
* The level of access this permission is protecting, as per
* {@link android.R.attr#protectionLevel}. Consists of
- * a base permission type and zero or more flags:
+ * a base permission type and zero or more flags. Use the following functions
+ * to extract them.
*
* <pre>
- * int basePermissionType = protectionLevel & {@link #PROTECTION_MASK_BASE};
- * int permissionFlags = protectionLevel & {@link #PROTECTION_MASK_FLAGS};
+ * int basePermissionType = permissionInfo.getProtection();
+ * int permissionFlags = permissionInfo.getProtectionFlags();
* </pre>
*
* <p></p>Base permission types are {@link #PROTECTION_NORMAL},
* {@link #PROTECTION_DANGEROUS}, {@link #PROTECTION_SIGNATURE}
* and the deprecated {@link #PROTECTION_SIGNATURE_OR_SYSTEM}.
* Flags are listed under {@link android.R.attr#protectionLevel}.
+ *
+ * @deprecated Use #getProtection() and #getProtectionFlags() instead.
*/
+ @Deprecated
public int protectionLevel;
/**
@@ -304,6 +359,9 @@
if ((level & PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED) != 0) {
protLevel += "|vendorPrivileged";
}
+ if ((level & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) != 0) {
+ protLevel += "|textClassifier";
+ }
return protLevel;
}
@@ -344,6 +402,22 @@
return null;
}
+ /**
+ * Return the base permission type.
+ */
+ @Protection
+ public int getProtection() {
+ return protectionLevel & PROTECTION_MASK_BASE;
+ }
+
+ /**
+ * Return the additional flags in {@link #protectionLevel}.
+ */
+ @ProtectionFlags
+ public int getProtectionFlags() {
+ return protectionLevel & ~PROTECTION_MASK_BASE;
+ }
+
@Override
public String toString() {
return "PermissionInfo{"
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3ed533a..a1a14b9 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -24,6 +24,7 @@
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.TypeReference;
+import android.hardware.camera2.utils.SurfaceUtils;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -643,6 +644,30 @@
break;
}
}
+
+ if (!streamFound) {
+ // Check if we can match s by native object ID
+ long reqSurfaceId = SurfaceUtils.getSurfaceId(s);
+ for (int j = 0; j < configuredOutputs.size(); ++j) {
+ int streamId = configuredOutputs.keyAt(j);
+ OutputConfiguration outConfig = configuredOutputs.valueAt(j);
+ int surfaceId = 0;
+ for (Surface outSurface : outConfig.getSurfaces()) {
+ if (reqSurfaceId == SurfaceUtils.getSurfaceId(outSurface)) {
+ streamFound = true;
+ mStreamIdxArray[i] = streamId;
+ mSurfaceIdxArray[i] = surfaceId;
+ i++;
+ break;
+ }
+ surfaceId++;
+ }
+ if (streamFound) {
+ break;
+ }
+ }
+ }
+
if (!streamFound) {
mStreamIdxArray = null;
mSurfaceIdxArray = null;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index e7f2134..71a361b 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -730,7 +730,7 @@
LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
}
- static long getSurfaceId(Surface surface) throws BufferQueueAbandonedException {
+ public static long getSurfaceId(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
try {
return nativeGetSurfaceId(surface);
diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
index e1e1c4f..9247844 100644
--- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java
+++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
@@ -56,6 +56,20 @@
}
/**
+ * Get the native object id of a surface.
+ *
+ * @param surface The surface to be checked.
+ * @return the native object id of the surface, 0 if surface is not backed by a native object.
+ */
+ public static long getSurfaceId(Surface surface) {
+ try {
+ return LegacyCameraDevice.getSurfaceId(surface);
+ } catch (BufferQueueAbandonedException e) {
+ return 0;
+ }
+ }
+
+ /**
* Get the Surface size.
*
* @param surface The surface to be queried for size.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 74a36df..572c585 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -454,8 +454,12 @@
/**
* Opens a file descriptor for reading and writing data to the USB accessory.
*
+ * <p>If data is read from the {@link java.io.InputStream} created from this file descriptor all
+ * data of a USB transfer should be read at once. If only a partial request is read the rest of
+ * the transfer is dropped.
+ *
* @param accessory the USB accessory to open
- * @return file descriptor, or null if the accessor could not be opened.
+ * @return file descriptor, or null if the accessory could not be opened.
*/
@RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 6ecf6d5..dc46b73 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -240,8 +240,11 @@
* New in version 30:
* - Uid.PROCESS_STATE_FOREGROUND_SERVICE only tracks
* ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE.
+ * New in version 31:
+ * - New cellular network types.
+ * - Deferred job metrics.
*/
- static final int CHECKIN_VERSION = 30;
+ static final int CHECKIN_VERSION = 31;
/**
* Old version, we hit 9 and ran out of room, need to remove.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2ec9009e..68490ed 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10471,7 +10471,8 @@
/**
* TextClassifier specific settings.
- * This is encoded as a key=value list, separated by commas. Ex:
+ * This is encoded as a key=value list, separated by commas. String[] types like
+ * entity_list_default use ":" as delimiter for values. Ex:
*
* <pre>
* smart_selection_dark_launch (boolean)
@@ -10479,6 +10480,10 @@
* suggest_selection_max_range_length (int)
* classify_text_max_range_length (int)
* generate_links_max_text_length (int)
+ * generate_links_log_sample_rate (int)
+ * entity_list_default (String[])
+ * entity_list_not_editable (String[])
+ * entity_list_editable (String[])
* </pre>
*
* <p>
diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java
index 1ef6100..cf16749 100644
--- a/core/java/android/service/autofill/AutofillFieldClassificationService.java
+++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java
@@ -15,12 +15,15 @@
*/
package android.service.autofill;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
@@ -30,9 +33,6 @@
import android.util.Log;
import android.view.autofill.AutofillValue;
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
-
import java.util.Arrays;
import java.util.List;
@@ -55,8 +55,6 @@
private static final String TAG = "AutofillFieldClassificationService";
- private static final int MSG_GET_SCORES = 1;
-
/**
* The {@link Intent} action that must be declared as handled by a service
* in its manifest for the system to recognize it as a quota providing service.
@@ -83,35 +81,18 @@
private AutofillFieldClassificationServiceWrapper mWrapper;
- private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
- final int action = msg.what;
+ private void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
+ List<AutofillValue> actualValues, String[] userDataValues) {
final Bundle data = new Bundle();
- final RemoteCallback callback;
- switch (action) {
- case MSG_GET_SCORES:
- final SomeArgs args = (SomeArgs) msg.obj;
- callback = (RemoteCallback) args.arg1;
- final String algorithmName = (String) args.arg2;
- final Bundle algorithmArgs = (Bundle) args.arg3;
- @SuppressWarnings("unchecked")
- final List<AutofillValue> actualValues = ((List<AutofillValue>) args.arg4);
- @SuppressWarnings("unchecked")
- final String[] userDataValues = (String[]) args.arg5;
- final float[][] scores = onGetScores(algorithmName, algorithmArgs, actualValues,
- Arrays.asList(userDataValues));
- if (scores != null) {
- data.putParcelable(EXTRA_SCORES, new Scores(scores));
- }
- break;
- default:
- Log.w(TAG, "Handling unknown message: " + action);
- return;
+ final float[][] scores = onGetScores(algorithmName, algorithmArgs, actualValues,
+ Arrays.asList(userDataValues));
+ if (scores != null) {
+ data.putParcelable(EXTRA_SCORES, new Scores(scores));
}
callback.sendResult(data);
- };
+ }
- private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
- mHandlerCallback, true);
+ private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
/** @hide */
public AutofillFieldClassificationService() {
@@ -160,9 +141,10 @@
public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs,
List<AutofillValue> actualValues, String[] userDataValues)
throws RemoteException {
- // TODO(b/70939974): refactor to use PooledLambda
- mHandlerCaller.obtainMessageOOOOO(MSG_GET_SCORES, callback, algorithmName,
- algorithmArgs, actualValues, userDataValues).sendToTarget();
+ mHandler.sendMessage(obtainMessage(
+ AutofillFieldClassificationService::getScores,
+ AutofillFieldClassificationService.this,
+ callback, algorithmName, algorithmArgs, actualValues, userDataValues));
}
}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 7a304c7..0c5d8bd 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -15,6 +15,8 @@
*/
package android.service.autofill;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -22,6 +24,7 @@
import android.app.Service;
import android.content.Intent;
import android.os.CancellationSignal;
+import android.os.Handler;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.Looper;
@@ -34,9 +37,6 @@
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
-
/**
* An {@code AutofillService} is a service used to automatically fill the contents of the screen
* on behalf of a given user - for more information about autofill, read
@@ -554,20 +554,12 @@
*/
public static final String SERVICE_META_DATA = "android.autofill";
- // Handler messages.
- private static final int MSG_CONNECT = 1;
- private static final int MSG_DISCONNECT = 2;
- private static final int MSG_ON_FILL_REQUEST = 3;
- private static final int MSG_ON_SAVE_REQUEST = 4;
-
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
public void onConnectedStateChanged(boolean connected) {
- if (connected) {
- mHandlerCaller.obtainMessage(MSG_CONNECT).sendToTarget();
- } else {
- mHandlerCaller.obtainMessage(MSG_DISCONNECT).sendToTarget();
- }
+ mHandler.sendMessage(obtainMessage(
+ connected ? AutofillService::onConnected : AutofillService::onDisconnected,
+ AutofillService.this));
}
@Override
@@ -578,56 +570,27 @@
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- mHandlerCaller.obtainMessageOOO(MSG_ON_FILL_REQUEST, request,
- CancellationSignal.fromTransport(transport), callback)
- .sendToTarget();
+ mHandler.sendMessage(obtainMessage(
+ AutofillService::onFillRequest,
+ AutofillService.this, request, CancellationSignal.fromTransport(transport),
+ new FillCallback(callback, request.getId())));
}
@Override
public void onSaveRequest(SaveRequest request, ISaveCallback callback) {
- mHandlerCaller.obtainMessageOO(MSG_ON_SAVE_REQUEST, request,
- callback).sendToTarget();
+ mHandler.sendMessage(obtainMessage(
+ AutofillService::onSaveRequest,
+ AutofillService.this, request, new SaveCallback(callback)));
}
};
- private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
- switch (msg.what) {
- case MSG_CONNECT: {
- onConnected();
- break;
- } case MSG_ON_FILL_REQUEST: {
- final SomeArgs args = (SomeArgs) msg.obj;
- final FillRequest request = (FillRequest) args.arg1;
- final CancellationSignal cancellation = (CancellationSignal) args.arg2;
- final IFillCallback callback = (IFillCallback) args.arg3;
- final FillCallback fillCallback = new FillCallback(callback, request.getId());
- args.recycle();
- onFillRequest(request, cancellation, fillCallback);
- break;
- } case MSG_ON_SAVE_REQUEST: {
- final SomeArgs args = (SomeArgs) msg.obj;
- final SaveRequest request = (SaveRequest) args.arg1;
- final ISaveCallback callback = (ISaveCallback) args.arg2;
- final SaveCallback saveCallback = new SaveCallback(callback);
- args.recycle();
- onSaveRequest(request, saveCallback);
- break;
- } case MSG_DISCONNECT: {
- onDisconnected();
- break;
- } default: {
- Log.w(TAG, "MyCallbacks received invalid message type: " + msg);
- }
- }
- };
-
- private HandlerCaller mHandlerCaller;
+ private Handler mHandler;
@CallSuper
@Override
public void onCreate() {
super.onCreate();
- mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true);
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
}
@Override
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 6c8c8bc..2c8c4ec 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -37,8 +38,6 @@
import android.view.textclassifier.TextLinks;
import android.view.textclassifier.TextSelection;
-import com.android.internal.R;
-
/**
* Abstract base class for the TextClassifier service.
*
@@ -263,28 +262,33 @@
*/
@Nullable
public static ComponentName getServiceComponentName(Context context) {
- final String str = context.getString(R.string.config_defaultTextClassifierService);
- if (!TextUtils.isEmpty(str)) {
- try {
- final ComponentName componentName = ComponentName.unflattenFromString(str);
- final Intent intent = new Intent(SERVICE_INTERFACE).setComponent(componentName);
- final ServiceInfo si = context.getPackageManager()
- .getServiceInfo(intent.getComponent(), 0);
- final String permission = si == null ? null : si.permission;
- if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) {
- return componentName;
- }
- Slog.w(LOG_TAG, String.format(
- "Service %s should require %s permission. Found %s permission",
- intent.getComponent().flattenToString(),
- Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE,
- si.permission));
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(LOG_TAG, String.format("Service %s not found", str));
- }
- } else {
+ final String packageName = context.getPackageManager().getSystemTextClassifierPackageName();
+ if (TextUtils.isEmpty(packageName)) {
Slog.d(LOG_TAG, "No configured system TextClassifierService");
+ return null;
}
+
+ final Intent intent = new Intent(SERVICE_INTERFACE).setPackage(packageName);
+
+ final ResolveInfo ri = context.getPackageManager().resolveService(intent,
+ PackageManager.MATCH_SYSTEM_ONLY);
+
+ if ((ri == null) || (ri.serviceInfo == null)) {
+ Slog.w(LOG_TAG, String.format("Package or service not found in package %s",
+ packageName));
+ return null;
+ }
+ final ServiceInfo si = ri.serviceInfo;
+
+ final String permission = si.permission;
+ if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) {
+ return si.getComponentName();
+ }
+ Slog.w(LOG_TAG, String.format(
+ "Service %s should require %s permission. Found %s permission",
+ si.getComponentName(),
+ Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE,
+ si.permission));
return null;
}
}
diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java
index 9b0489c..fa98096 100644
--- a/core/java/android/util/LongArray.java
+++ b/core/java/android/util/LongArray.java
@@ -16,11 +16,15 @@
package android.util;
+import android.annotation.Nullable;
+
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
-import java.util.Arrays;
+
import libcore.util.EmptyArray;
+import java.util.Arrays;
+
/**
* Implements a growing array of long primitives.
*
@@ -216,4 +220,18 @@
throw new ArrayIndexOutOfBoundsException(mSize, index);
}
}
+
+ /**
+ * Test if each element of {@code a} equals corresponding element from {@code b}
+ */
+ public static boolean elementsEqual(@Nullable LongArray a, @Nullable LongArray b) {
+ if (a == null || b == null) return a == b;
+ if (a.mSize != b.mSize) return false;
+ for (int i = 0; i < a.mSize; i++) {
+ if (a.get(i) != b.get(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java
index 7b89967..3b8fc5c 100644
--- a/core/java/android/util/apk/ApkVerityBuilder.java
+++ b/core/java/android/util/apk/ApkVerityBuilder.java
@@ -331,7 +331,7 @@
buffer.putShort((short) 1); // meta algorithm, SHA256_MODE == 1
buffer.putShort((short) 1); // data algorithm, SHA256_MODE == 1
- buffer.putInt(0x1); // flags, 0x1: has extension
+ buffer.putInt(0x0); // flags
buffer.putInt(0); // reserved
buffer.putLong(fileSize); // original file size
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
index 8f9ae0e..671532c 100644
--- a/core/java/android/view/DisplayListCanvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -198,8 +198,8 @@
*
* @param layer The layer to composite on this canvas
*/
- void drawHardwareLayer(HardwareLayer layer) {
- nDrawLayer(mNativeCanvasWrapper, layer.getLayerHandle());
+ void drawTextureLayer(TextureLayer layer) {
+ nDrawTextureLayer(mNativeCanvasWrapper, layer.getLayerHandle());
}
///////////////////////////////////////////////////////////////////////////
@@ -257,7 +257,7 @@
@CriticalNative
private static native void nDrawRenderNode(long renderer, long renderNode);
@CriticalNative
- private static native void nDrawLayer(long renderer, long layer);
+ private static native void nDrawTextureLayer(long renderer, long layer);
@CriticalNative
private static native void nDrawCircle(long renderer, long propCx,
long propCy, long propRadius, long propPaint);
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/TextureLayer.java
similarity index 88%
rename from core/java/android/view/HardwareLayer.java
rename to core/java/android/view/TextureLayer.java
index 7af1020..35a886f 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/TextureLayer.java
@@ -25,19 +25,17 @@
import com.android.internal.util.VirtualRefBasePtr;
/**
- * A hardware layer can be used to render graphics operations into a hardware
- * friendly buffer. For instance, with an OpenGL backend a hardware layer
- * would use a Frame Buffer Object (FBO.) The hardware layer can be used as
- * a drawing cache when a complex set of graphics operations needs to be
- * drawn several times.
+ * TextureLayer represents a SurfaceTexture that will be composited by RenderThread into the
+ * frame when drawn in a HW accelerated Canvas. This is backed by a DeferredLayerUpdater on
+ * the native side.
*
* @hide
*/
-final class HardwareLayer {
+final class TextureLayer {
private ThreadedRenderer mRenderer;
private VirtualRefBasePtr mFinalizer;
- private HardwareLayer(ThreadedRenderer renderer, long deferredUpdater) {
+ private TextureLayer(ThreadedRenderer renderer, long deferredUpdater) {
if (renderer == null || deferredUpdater == 0) {
throw new IllegalArgumentException("Either hardware renderer: " + renderer
+ " or deferredUpdater: " + deferredUpdater + " is invalid");
@@ -141,11 +139,12 @@
mRenderer.pushLayerUpdate(this);
}
- static HardwareLayer adoptTextureLayer(ThreadedRenderer renderer, long layer) {
- return new HardwareLayer(renderer, layer);
+ static TextureLayer adoptTextureLayer(ThreadedRenderer renderer, long layer) {
+ return new TextureLayer(renderer, layer);
}
- private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque);
+ private static native boolean nPrepare(long layerUpdater, int width, int height,
+ boolean isOpaque);
private static native void nSetLayerPaint(long layerUpdater, long paint);
private static native void nSetTransform(long layerUpdater, long matrix);
private static native void nSetSurfaceTexture(long layerUpdater, SurfaceTexture surface);
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 25dce99..371794045 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -106,7 +106,7 @@
public class TextureView extends View {
private static final String LOG_TAG = "TextureView";
- private HardwareLayer mLayer;
+ private TextureLayer mLayer;
private SurfaceTexture mSurface;
private SurfaceTextureListener mListener;
private boolean mHadSurface;
@@ -336,13 +336,13 @@
if (canvas.isHardwareAccelerated()) {
DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
- HardwareLayer layer = getHardwareLayer();
+ TextureLayer layer = getTextureLayer();
if (layer != null) {
applyUpdate();
applyTransformMatrix();
mLayer.setLayerPaint(mLayerPaint); // ensure layer paint is up to date
- displayListCanvas.drawHardwareLayer(layer);
+ displayListCanvas.drawTextureLayer(layer);
}
}
}
@@ -369,7 +369,7 @@
}
}
- HardwareLayer getHardwareLayer() {
+ TextureLayer getTextureLayer() {
if (mLayer == null) {
if (mAttachInfo == null || mAttachInfo.mThreadedRenderer == null) {
return null;
@@ -602,7 +602,7 @@
// the layer here thanks to the validate() call at the beginning of
// this method
if (mLayer == null && mUpdateSurface) {
- getHardwareLayer();
+ getTextureLayer();
}
if (mLayer != null) {
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index e50d40e..6da51d1 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -34,6 +34,7 @@
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
+import android.view.animation.AnimationUtils;
import com.android.internal.R;
import com.android.internal.util.VirtualRefBasePtr;
@@ -833,9 +834,9 @@
*
* @return A hardware layer
*/
- HardwareLayer createTextureLayer() {
+ TextureLayer createTextureLayer() {
long layer = nCreateTextureLayer(mNativeProxy);
- return HardwareLayer.adoptTextureLayer(this, layer);
+ return TextureLayer.adoptTextureLayer(this, layer);
}
@@ -844,7 +845,7 @@
}
- boolean copyLayerInto(final HardwareLayer layer, final Bitmap bitmap) {
+ boolean copyLayerInto(final TextureLayer layer, final Bitmap bitmap) {
return nCopyLayerInto(mNativeProxy,
layer.getDeferredLayerUpdater(), bitmap);
}
@@ -855,7 +856,7 @@
*
* @param layer The hardware layer that needs an update
*/
- void pushLayerUpdate(HardwareLayer layer) {
+ void pushLayerUpdate(TextureLayer layer) {
nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
}
@@ -863,7 +864,7 @@
* Tells the HardwareRenderer that the layer is destroyed. The renderer
* should remove the layer from any update queues.
*/
- void onLayerDestroyed(HardwareLayer layer) {
+ void onLayerDestroyed(TextureLayer layer) {
nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
}
@@ -943,6 +944,109 @@
}
}
+ /**
+ * Basic synchronous renderer. Currently only used to render the Magnifier, so use with care.
+ * TODO: deduplicate against ThreadedRenderer.
+ *
+ * @hide
+ */
+ public static class SimpleRenderer {
+ private final RenderNode mRootNode;
+ private long mNativeProxy;
+ private final float mLightY, mLightZ;
+ private Surface mSurface;
+ private final FrameInfo mFrameInfo = new FrameInfo();
+
+ public SimpleRenderer(final Context context, final String name, final Surface surface) {
+ final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
+ mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
+ mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
+ final float lightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
+ final int ambientShadowAlpha =
+ (int) (255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0) + 0.5f);
+ final int spotShadowAlpha =
+ (int) (255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0) + 0.5f);
+ a.recycle();
+
+ final long rootNodePtr = nCreateRootRenderNode();
+ mRootNode = RenderNode.adopt(rootNodePtr);
+ mRootNode.setClipToBounds(false);
+ mNativeProxy = nCreateProxy(true /* translucent */, rootNodePtr);
+ nSetName(mNativeProxy, name);
+
+ ProcessInitializer.sInstance.init(context, mNativeProxy);
+ nLoadSystemProperties(mNativeProxy);
+
+ nSetup(mNativeProxy, lightRadius, ambientShadowAlpha, spotShadowAlpha);
+
+ mSurface = surface;
+ nUpdateSurface(mNativeProxy, surface);
+ }
+
+ /**
+ * Set the light center.
+ */
+ public void setLightCenter(final Display display,
+ final int windowLeft, final int windowTop) {
+ // Adjust light position for window offsets.
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+ final float lightX = displaySize.x / 2f - windowLeft;
+ final float lightY = mLightY - windowTop;
+
+ nSetLightCenter(mNativeProxy, lightX, lightY, mLightZ);
+ }
+
+ public RenderNode getRootNode() {
+ return mRootNode;
+ }
+
+ /**
+ * Draw the surface.
+ */
+ public void draw(final FrameDrawingCallback callback) {
+ final long vsync = AnimationUtils.currentAnimationTimeMillis() * 1000000L;
+ mFrameInfo.setVsync(vsync, vsync);
+ mFrameInfo.addFlags(1 << 2 /* VSYNC */);
+ // TODO: remove this fence
+ nFence(mNativeProxy);
+ if (callback != null) {
+ callback.onFrameDraw(mSurface.getNextFrameNumber());
+ }
+ nSyncAndDrawFrame(mNativeProxy, mFrameInfo.mFrameInfo, mFrameInfo.mFrameInfo.length);
+ }
+
+ /**
+ * Destroy the renderer.
+ */
+ public void destroy() {
+ mSurface = null;
+ nDestroy(mNativeProxy, mRootNode.mNativeRenderNode);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nDeleteProxy(mNativeProxy);
+ mNativeProxy = 0;
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+ /**
+ * Interface used to receive callbacks when a frame is being drawn.
+ */
+ public interface FrameDrawingCallback {
+ /**
+ * Invoked during a frame drawing.
+ *
+ * @param frame The id of the frame being drawn.
+ */
+ void onFrameDraw(long frame);
+ }
+
private static class ProcessInitializer {
static ProcessInitializer sInstance = new ProcessInitializer();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3ff3c97..1e4f568 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -18055,19 +18055,20 @@
* currently attached to.
*/
public WindowId getWindowId() {
- if (mAttachInfo == null) {
+ AttachInfo ai = mAttachInfo;
+ if (ai == null) {
return null;
}
- if (mAttachInfo.mWindowId == null) {
+ if (ai.mWindowId == null) {
try {
- mAttachInfo.mIWindowId = mAttachInfo.mSession.getWindowId(
- mAttachInfo.mWindowToken);
- mAttachInfo.mWindowId = new WindowId(
- mAttachInfo.mIWindowId);
+ ai.mIWindowId = ai.mSession.getWindowId(ai.mWindowToken);
+ if (ai.mIWindowId != null) {
+ ai.mWindowId = new WindowId(ai.mIWindowId);
+ }
} catch (RemoteException e) {
}
}
- return mAttachInfo.mWindowId;
+ return ai.mWindowId;
}
/**
diff --git a/core/java/android/view/WindowId.java b/core/java/android/view/WindowId.java
index c4cda2c..12e58f1 100644
--- a/core/java/android/view/WindowId.java
+++ b/core/java/android/view/WindowId.java
@@ -16,6 +16,8 @@
package android.view;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -35,6 +37,7 @@
* that doesn't allow the other process to negatively harm your window.
*/
public class WindowId implements Parcelable {
+ @NonNull
private final IWindowId mToken;
/**
@@ -74,8 +77,7 @@
}
};
- final HashMap<IBinder, WindowId> mRegistrations
- = new HashMap<IBinder, WindowId>();
+ final HashMap<IBinder, WindowId> mRegistrations = new HashMap<>();
class H extends Handler {
@Override
@@ -163,10 +165,9 @@
* same package.
*/
@Override
- public boolean equals(Object otherObj) {
+ public boolean equals(@Nullable Object otherObj) {
if (otherObj instanceof WindowId) {
- return mToken.asBinder().equals(((WindowId) otherObj)
- .mToken.asBinder());
+ return mToken.asBinder().equals(((WindowId) otherObj).mToken.asBinder());
}
return false;
}
@@ -182,7 +183,7 @@
sb.append("IntentSender{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(": ");
- sb.append(mToken != null ? mToken.asBinder() : null);
+ sb.append(mToken.asBinder());
sb.append('}');
return sb.toString();
}
@@ -195,30 +196,32 @@
out.writeStrongBinder(mToken.asBinder());
}
- public static final Parcelable.Creator<WindowId> CREATOR
- = new Parcelable.Creator<WindowId>() {
+ public static final Parcelable.Creator<WindowId> CREATOR = new Parcelable.Creator<WindowId>() {
+ @Override
public WindowId createFromParcel(Parcel in) {
IBinder target = in.readStrongBinder();
return target != null ? new WindowId(target) : null;
}
+ @Override
public WindowId[] newArray(int size) {
return new WindowId[size];
}
};
/** @hide */
+ @NonNull
public IWindowId getTarget() {
return mToken;
}
/** @hide */
- public WindowId(IWindowId target) {
+ public WindowId(@NonNull IWindowId target) {
mToken = target;
}
/** @hide */
- public WindowId(IBinder target) {
+ public WindowId(@NonNull IBinder target) {
mToken = IWindowId.Stub.asInterface(target);
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 417a725..5b1dd5c 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3203,7 +3203,7 @@
fieldIndex++;
if (mConnectionId != DEFAULT.mConnectionId) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
- if (!Objects.equals(mChildNodeIds, DEFAULT.mChildNodeIds)) {
+ if (!LongArray.elementsEqual(mChildNodeIds, DEFAULT.mChildNodeIds)) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 474db12..64686dd 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -206,6 +206,8 @@
*/
private boolean mDetachWallpaper = false;
+ private boolean mShowWallpaper;
+
private boolean mMore = true;
private boolean mOneMoreTime = true;
@@ -253,7 +255,10 @@
setBackgroundColor(a.getInt(com.android.internal.R.styleable.Animation_background, 0));
- setDetachWallpaper(a.getBoolean(com.android.internal.R.styleable.Animation_detachWallpaper, false));
+ setDetachWallpaper(
+ a.getBoolean(com.android.internal.R.styleable.Animation_detachWallpaper, false));
+ setShowWallpaper(
+ a.getBoolean(com.android.internal.R.styleable.Animation_showWallpaper, false));
final int resID = a.getResourceId(com.android.internal.R.styleable.Animation_interpolator, 0);
@@ -661,6 +666,18 @@
}
/**
+ * If this animation is run as a window animation, this will make the wallpaper visible behind
+ * the animation.
+ *
+ * @param showWallpaper Whether the wallpaper should be shown during the animation.
+ * @attr ref android.R.styleable#Animation_detachWallpaper
+ * @hide
+ */
+ public void setShowWallpaper(boolean showWallpaper) {
+ mShowWallpaper = showWallpaper;
+ }
+
+ /**
* Gets the acceleration curve type for this animation.
*
* @return the {@link Interpolator} associated to this animation
@@ -775,6 +792,16 @@
}
/**
+ * @return If run as a window animation, returns whether the wallpaper will be shown behind
+ * during the animation.
+ * @attr ref android.R.styleable#Animation_showWallpaper
+ * @hide
+ */
+ public boolean getShowWallpaper() {
+ return mShowWallpaper;
+ }
+
+ /**
* <p>Indicates whether or not this animation will affect the transformation
* matrix. For instance, a fade animation will not affect the matrix whereas
* a scale animation will.</p>
diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
deleted file mode 100644
index 69c38ee..0000000
--- a/core/java/android/view/textclassifier/SmartSelection.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.textclassifier;
-
-import android.annotation.Nullable;
-import android.content.res.AssetFileDescriptor;
-
-/**
- * Java wrapper for SmartSelection native library interface.
- * This library is used for detecting entities in text.
- */
-final class SmartSelection {
-
- static {
- System.loadLibrary("textclassifier");
- }
-
- /** Hints the classifier that this may be a url. */
- static final int HINT_FLAG_URL = 0x01;
- /** Hints the classifier that this may be an email. */
- static final int HINT_FLAG_EMAIL = 0x02;
-
- private final long mCtx;
-
- /**
- * Creates a new instance of SmartSelect predictor, using the provided model image,
- * given as a file descriptor.
- */
- SmartSelection(int fd) {
- mCtx = nativeNew(fd);
- }
-
- /**
- * Creates a new instance of SmartSelect predictor, using the provided model image, given as a
- * file path.
- */
- SmartSelection(String path) {
- mCtx = nativeNewFromPath(path);
- }
-
- /**
- * Creates a new instance of SmartSelect predictor, using the provided model image, given as an
- * AssetFileDescriptor.
- */
- SmartSelection(AssetFileDescriptor afd) {
- mCtx = nativeNewFromAssetFileDescriptor(afd, afd.getStartOffset(), afd.getLength());
- if (mCtx == 0L) {
- throw new IllegalArgumentException(
- "Couldn't initialize TC from given AssetFileDescriptor");
- }
- }
-
- /**
- * Given a string context and current selection, computes the SmartSelection suggestion.
- *
- * The begin and end are character indices into the context UTF8 string. selectionBegin is the
- * character index where the selection begins, and selectionEnd is the index of one character
- * past the selection span.
- *
- * The return value is an array of two ints: suggested selection beginning and end, with the
- * same semantics as the input selectionBeginning and selectionEnd.
- */
- public int[] suggest(String context, int selectionBegin, int selectionEnd) {
- return nativeSuggest(mCtx, context, selectionBegin, selectionEnd);
- }
-
- /**
- * Given a string context and current selection, classifies the type of the selected text.
- *
- * The begin and end params are character indices in the context string.
- *
- * Returns an array of ClassificationResult objects with the probability
- * scores for different collections.
- */
- public ClassificationResult[] classifyText(
- String context, int selectionBegin, int selectionEnd, int hintFlags) {
- return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd, hintFlags);
- }
-
- /**
- * Annotates given input text. Every word of the input is a part of some annotation.
- * The annotations are sorted by their position in the context string.
- * The annotations do not overlap.
- */
- public AnnotatedSpan[] annotate(String text) {
- return nativeAnnotate(mCtx, text);
- }
-
- /**
- * Frees up the allocated memory.
- */
- public void close() {
- nativeClose(mCtx);
- }
-
- /**
- * Returns a comma separated list of locales supported by the model as BCP 47 tags.
- */
- public static String getLanguages(int fd) {
- return nativeGetLanguage(fd);
- }
-
- /**
- * Returns the version of the model.
- */
- public static int getVersion(int fd) {
- return nativeGetVersion(fd);
- }
-
- private static native long nativeNew(int fd);
-
- private static native long nativeNewFromPath(String path);
-
- private static native long nativeNewFromAssetFileDescriptor(AssetFileDescriptor afd,
- long offset, long size);
-
- private static native int[] nativeSuggest(
- long context, String text, int selectionBegin, int selectionEnd);
-
- private static native ClassificationResult[] nativeClassifyText(
- long context, String text, int selectionBegin, int selectionEnd, int hintFlags);
-
- private static native AnnotatedSpan[] nativeAnnotate(long context, String text);
-
- private static native void nativeClose(long context);
-
- private static native String nativeGetLanguage(int fd);
-
- private static native int nativeGetVersion(int fd);
-
- /** Classification result for classifyText method. */
- static final class ClassificationResult {
- final String mCollection;
- /** float range: 0 - 1 */
- final float mScore;
- @Nullable final DatetimeParseResult mDatetime;
-
- ClassificationResult(String collection, float score) {
- mCollection = collection;
- mScore = score;
- mDatetime = null;
- }
-
- ClassificationResult(String collection, float score, DatetimeParseResult datetime) {
- mCollection = collection;
- mScore = score;
- mDatetime = datetime;
- }
- }
-
- /** Parsed date information for the classification result. */
- static final class DatetimeParseResult {
- long mMsSinceEpoch;
- }
-
- /** Represents a result of Annotate call. */
- public static final class AnnotatedSpan {
- final int mStartIndex;
- final int mEndIndex;
- final ClassificationResult[] mClassification;
-
- AnnotatedSpan(int startIndex, int endIndex, ClassificationResult[] classification) {
- mStartIndex = startIndex;
- mEndIndex = endIndex;
- mClassification = classification;
- }
-
- public int getStartIndex() {
- return mStartIndex;
- }
-
- public int getEndIndex() {
- return mEndIndex;
- }
-
- public ClassificationResult[] getClassification() {
- return mClassification;
- }
- }
-}
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index cbc3828..1789edf 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -41,11 +41,13 @@
private final ITextClassifierService mManagerService;
private final TextClassifier mFallback;
+ private final String mPackageName;
SystemTextClassifier(Context context) throws ServiceManager.ServiceNotFoundException {
mManagerService = ITextClassifierService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
mFallback = new TextClassifierImpl(context);
+ mPackageName = context.getPackageName();
}
/**
@@ -107,6 +109,11 @@
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
Utils.validate(text, false /* allowInMainThread */);
try {
+ if (options == null) {
+ options = new TextLinks.Options().setCallingPackageName(mPackageName);
+ } else if (!mPackageName.equals(options.getCallingPackageName())) {
+ options.setCallingPackageName(mPackageName);
+ }
final TextLinksCallback callback = new TextLinksCallback();
mManagerService.onGenerateLinks(text, options, callback);
final TextLinks links = callback.mReceiver.get();
diff --git a/core/java/android/view/textclassifier/TextClassifierConstants.java b/core/java/android/view/textclassifier/TextClassifierConstants.java
index efa6948..397473b 100644
--- a/core/java/android/view/textclassifier/TextClassifierConstants.java
+++ b/core/java/android/view/textclassifier/TextClassifierConstants.java
@@ -20,6 +20,11 @@
import android.util.KeyValueListParser;
import android.util.Slog;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringJoiner;
+
/**
* TextClassifier specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
@@ -27,6 +32,12 @@
* <pre>
* smart_selection_dark_launch (boolean)
* smart_selection_enabled_for_edit_text (boolean)
+ * suggest_selection_max_range_length (int)
+ * classify_text_max_range_length (int)
+ * generate_links_max_text_length (int)
+ * entity_list_default (String[])
+ * entity_list_not_editable (String[])
+ * entity_list_editable (String[])
* </pre>
*
* <p>
@@ -34,7 +45,9 @@
* see also android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS
*
* Example of setting the values for testing.
- * adb shell settings put global text_classifier_constants smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true
+ * adb shell settings put global text_classifier_constants \
+ * smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true,\
+ * entity_list_default=phone:address
* @hide
*/
public final class TextClassifierConstants {
@@ -53,6 +66,14 @@
"classify_text_max_range_length";
private static final String GENERATE_LINKS_MAX_TEXT_LENGTH =
"generate_links_max_text_length";
+ private static final String GENERATE_LINKS_LOG_SAMPLE_RATE =
+ "generate_links_log_sample_rate";
+ private static final String ENTITY_LIST_DEFAULT =
+ "entity_list_default";
+ private static final String ENTITY_LIST_NOT_EDITABLE =
+ "entity_list_not_editable";
+ private static final String ENTITY_LIST_EDITABLE =
+ "entity_list_editable";
private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false;
private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true;
@@ -60,6 +81,16 @@
private static final int SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000;
+ private static final int GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT = 100;
+ private static final String ENTITY_LIST_DELIMITER = ":";
+ private static final String ENTITY_LIST_DEFAULT_VALUE = new StringJoiner(ENTITY_LIST_DELIMITER)
+ .add(TextClassifier.TYPE_ADDRESS)
+ .add(TextClassifier.TYPE_EMAIL)
+ .add(TextClassifier.TYPE_PHONE)
+ .add(TextClassifier.TYPE_URL)
+ .add(TextClassifier.TYPE_DATE)
+ .add(TextClassifier.TYPE_DATE_TIME)
+ .add(TextClassifier.TYPE_FLIGHT_NUMBER).toString();
/** Default settings. */
static final TextClassifierConstants DEFAULT = new TextClassifierConstants();
@@ -70,6 +101,10 @@
private final int mSuggestSelectionMaxRangeLength;
private final int mClassifyTextMaxRangeLength;
private final int mGenerateLinksMaxTextLength;
+ private final int mGenerateLinksLogSampleRate;
+ private final List<String> mEntityListDefault;
+ private final List<String> mEntityListNotEditable;
+ private final List<String> mEntityListEditable;
private TextClassifierConstants() {
mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT;
@@ -78,6 +113,10 @@
mSuggestSelectionMaxRangeLength = SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT;
mClassifyTextMaxRangeLength = CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT;
mGenerateLinksMaxTextLength = GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT;
+ mGenerateLinksLogSampleRate = GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT;
+ mEntityListDefault = parseEntityList(ENTITY_LIST_DEFAULT_VALUE);
+ mEntityListNotEditable = mEntityListDefault;
+ mEntityListEditable = mEntityListDefault;
}
private TextClassifierConstants(@Nullable String settings) {
@@ -106,9 +145,22 @@
mGenerateLinksMaxTextLength = parser.getInt(
GENERATE_LINKS_MAX_TEXT_LENGTH,
GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
+ mGenerateLinksLogSampleRate = parser.getInt(
+ GENERATE_LINKS_LOG_SAMPLE_RATE,
+ GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
+ mEntityListDefault = parseEntityList(parser.getString(
+ ENTITY_LIST_DEFAULT,
+ ENTITY_LIST_DEFAULT_VALUE));
+ mEntityListNotEditable = parseEntityList(parser.getString(
+ ENTITY_LIST_NOT_EDITABLE,
+ ENTITY_LIST_DEFAULT_VALUE));
+ mEntityListEditable = parseEntityList(parser.getString(
+ ENTITY_LIST_EDITABLE,
+ ENTITY_LIST_DEFAULT_VALUE));
}
- static TextClassifierConstants loadFromString(String settings) {
+ /** Load from a settings string. */
+ public static TextClassifierConstants loadFromString(String settings) {
return new TextClassifierConstants(settings);
}
@@ -135,4 +187,24 @@
public int getGenerateLinksMaxTextLength() {
return mGenerateLinksMaxTextLength;
}
+
+ public int getGenerateLinksLogSampleRate() {
+ return mGenerateLinksLogSampleRate;
+ }
+
+ public List<String> getEntityListDefault() {
+ return mEntityListDefault;
+ }
+
+ public List<String> getEntityListNotEditable() {
+ return mEntityListNotEditable;
+ }
+
+ public List<String> getEntityListEditable() {
+ return mEntityListEditable;
+ }
+
+ private static List<String> parseEntityList(String listStr) {
+ return Collections.unmodifiableList(Arrays.asList(listStr.split(ENTITY_LIST_DELIMITER)));
+ }
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 20467ac..5b7095b 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -35,13 +35,11 @@
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.provider.Settings;
-import android.text.util.Linkify;
-import android.util.Patterns;
import android.view.textclassifier.logging.DefaultLogger;
+import android.view.textclassifier.logging.GenerateLinksLogger;
import android.view.textclassifier.logging.Logger;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import java.io.File;
@@ -81,20 +79,11 @@
private static final String MODEL_FILE_REGEX = "textclassifier\\.(.*)\\.model";
private static final String UPDATED_MODEL_FILE_PATH =
"/data/misc/textclassifier/textclassifier.model";
- private static final List<String> ENTITY_TYPES_ALL =
- Collections.unmodifiableList(Arrays.asList(
- TextClassifier.TYPE_ADDRESS,
- TextClassifier.TYPE_EMAIL,
- TextClassifier.TYPE_PHONE,
- TextClassifier.TYPE_URL,
- TextClassifier.TYPE_DATE,
- TextClassifier.TYPE_DATE_TIME,
- TextClassifier.TYPE_FLIGHT_NUMBER));
private final Context mContext;
private final TextClassifier mFallback;
- private final MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final GenerateLinksLogger mGenerateLinksLogger;
private final Object mLock = new Object();
@GuardedBy("mLock") // Do not access outside this lock.
@@ -102,7 +91,7 @@
@GuardedBy("mLock") // Do not access outside this lock.
private ModelFile mModel;
@GuardedBy("mLock") // Do not access outside this lock.
- private SmartSelection mSmartSelection;
+ private TextClassifierImplNative mNative;
private final Object mLoggerLock = new Object();
@GuardedBy("mLoggerLock") // Do not access outside this lock.
@@ -115,6 +104,8 @@
public TextClassifierImpl(Context context) {
mContext = Preconditions.checkNotNull(context);
mFallback = TextClassifier.NO_OP;
+ mGenerateLinksLogger = new GenerateLinksLogger(
+ getSettings().getGenerateLinksLogSampleRate());
}
/** @inheritDoc */
@@ -128,8 +119,10 @@
if (text.length() > 0
&& rangeLength <= getSettings().getSuggestSelectionMaxRangeLength()) {
final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+ final String localesString = concatenateLocales(locales);
+ final Calendar refTime = Calendar.getInstance();
final boolean darkLaunchAllowed = options != null && options.isDarkLaunchAllowed();
- final SmartSelection smartSelection = getSmartSelection(locales);
+ final TextClassifierImplNative nativeImpl = getNative(locales);
final String string = text.toString();
final int start;
final int end;
@@ -137,8 +130,9 @@
start = selectionStartIndex;
end = selectionEndIndex;
} else {
- final int[] startEnd = smartSelection.suggest(
- string, selectionStartIndex, selectionEndIndex);
+ final int[] startEnd = nativeImpl.suggestSelection(
+ string, selectionStartIndex, selectionEndIndex,
+ new TextClassifierImplNative.SelectionOptions(localesString));
start = startEnd[0];
end = startEnd[1];
}
@@ -146,13 +140,16 @@
&& start >= 0 && end <= string.length()
&& start <= selectionStartIndex && end >= selectionEndIndex) {
final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end);
- final SmartSelection.ClassificationResult[] results =
- smartSelection.classifyText(
+ final TextClassifierImplNative.ClassificationResult[] results =
+ nativeImpl.classifyText(
string, start, end,
- getHintFlags(string, start, end));
+ new TextClassifierImplNative.ClassificationOptions(
+ refTime.getTimeInMillis(),
+ refTime.getTimeZone().getID(),
+ localesString));
final int size = results.length;
for (int i = 0; i < size; i++) {
- tsBuilder.setEntityType(results[i].mCollection, results[i].mScore);
+ tsBuilder.setEntityType(results[i].getCollection(), results[i].getScore());
}
return tsBuilder
.setSignature(
@@ -185,10 +182,17 @@
if (text.length() > 0 && rangeLength <= getSettings().getClassifyTextMaxRangeLength()) {
final String string = text.toString();
final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
- final Calendar refTime = (options == null) ? null : options.getReferenceTime();
- final SmartSelection.ClassificationResult[] results = getSmartSelection(locales)
- .classifyText(string, startIndex, endIndex,
- getHintFlags(string, startIndex, endIndex));
+ final String localesString = concatenateLocales(locales);
+ final Calendar refTime = (options != null && options.getReferenceTime() != null)
+ ? options.getReferenceTime() : Calendar.getInstance();
+
+ final TextClassifierImplNative.ClassificationResult[] results =
+ getNative(locales)
+ .classifyText(string, startIndex, endIndex,
+ new TextClassifierImplNative.ClassificationOptions(
+ refTime.getTimeInMillis(),
+ refTime.getTimeZone().getID(),
+ localesString));
if (results.length > 0) {
return createClassificationResult(
results, string, startIndex, endIndex, refTime);
@@ -215,26 +219,45 @@
}
try {
+ final long startTimeMs = System.currentTimeMillis();
final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+ final Calendar refTime = Calendar.getInstance();
final Collection<String> entitiesToIdentify =
options != null && options.getEntityConfig() != null
? options.getEntityConfig().resolveEntityListModifications(
getEntitiesForHints(options.getEntityConfig().getHints()))
- : ENTITY_TYPES_ALL;
- final SmartSelection smartSelection = getSmartSelection(defaultLocales);
- final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString);
- for (SmartSelection.AnnotatedSpan span : annotations) {
- final SmartSelection.ClassificationResult[] results = span.getClassification();
- if (results.length == 0 || !entitiesToIdentify.contains(results[0].mCollection)) {
+ : getSettings().getEntityListDefault();
+ final TextClassifierImplNative nativeImpl =
+ getNative(defaultLocales);
+ final TextClassifierImplNative.AnnotatedSpan[] annotations =
+ nativeImpl.annotate(
+ textString,
+ new TextClassifierImplNative.AnnotationOptions(
+ refTime.getTimeInMillis(),
+ refTime.getTimeZone().getID(),
+ concatenateLocales(defaultLocales)));
+ for (TextClassifierImplNative.AnnotatedSpan span : annotations) {
+ final TextClassifierImplNative.ClassificationResult[] results =
+ span.getClassification();
+ if (results.length == 0
+ || !entitiesToIdentify.contains(results[0].getCollection())) {
continue;
}
final Map<String, Float> entityScores = new HashMap<>();
for (int i = 0; i < results.length; i++) {
- entityScores.put(results[i].mCollection, results[i].mScore);
+ entityScores.put(results[i].getCollection(), results[i].getScore());
}
builder.addLink(span.getStartIndex(), span.getEndIndex(), entityScores);
}
- return builder.build();
+ final TextLinks links = builder.build();
+ final long endTimeMs = System.currentTimeMillis();
+ final String callingPackageName =
+ options == null || options.getCallingPackageName() == null
+ ? mContext.getPackageName() // local (in process) TC.
+ : options.getCallingPackageName();
+ mGenerateLinksLogger.logGenerateLinks(
+ text, links, callingPackageName, endTimeMs - startTimeMs);
+ return links;
} catch (Throwable t) {
// Avoid throwing from this method. Log the error.
Log.e(LOG_TAG, "Error getting links info.", t);
@@ -249,7 +272,18 @@
}
private Collection<String> getEntitiesForHints(Collection<String> hints) {
- return ENTITY_TYPES_ALL;
+ final boolean editable = hints.contains(HINT_TEXT_IS_EDITABLE);
+ final boolean notEditable = hints.contains(HINT_TEXT_IS_NOT_EDITABLE);
+
+ // Use the default if there is no hint, or conflicting ones.
+ final boolean useDefault = editable == notEditable;
+ if (useDefault) {
+ return getSettings().getEntityListDefault();
+ } else if (editable) {
+ return getSettings().getEntityListEditable();
+ } else { // notEditable
+ return getSettings().getEntityListNotEditable();
+ }
}
@Override
@@ -274,23 +308,24 @@
return mSettings;
}
- private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException {
+ private TextClassifierImplNative getNative(LocaleList localeList)
+ throws FileNotFoundException {
synchronized (mLock) {
localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
final ModelFile bestModel = findBestModelLocked(localeList);
if (bestModel == null) {
throw new FileNotFoundException("No model for " + localeList.toLanguageTags());
}
- if (mSmartSelection == null || !Objects.equals(mModel, bestModel)) {
+ if (mNative == null || !Objects.equals(mModel, bestModel)) {
Log.d(DEFAULT_LOG_TAG, "Loading " + bestModel);
- destroySmartSelectionIfExistsLocked();
+ destroyNativeIfExistsLocked();
final ParcelFileDescriptor fd = ParcelFileDescriptor.open(
new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY);
- mSmartSelection = new SmartSelection(fd.getFd());
+ mNative = new TextClassifierImplNative(fd.getFd());
closeAndLogError(fd);
mModel = bestModel;
}
- return mSmartSelection;
+ return mNative;
}
}
@@ -302,13 +337,17 @@
}
@GuardedBy("mLock") // Do not call outside this lock.
- private void destroySmartSelectionIfExistsLocked() {
- if (mSmartSelection != null) {
- mSmartSelection.close();
- mSmartSelection = null;
+ private void destroyNativeIfExistsLocked() {
+ if (mNative != null) {
+ mNative.close();
+ mNative = null;
}
}
+ private static String concatenateLocales(@Nullable LocaleList locales) {
+ return (locales == null) ? "" : locales.toLanguageTags();
+ }
+
/**
* Finds the most appropriate model to use for the given target locale list.
*
@@ -372,20 +411,21 @@
}
private TextClassification createClassificationResult(
- SmartSelection.ClassificationResult[] classifications,
+ TextClassifierImplNative.ClassificationResult[] classifications,
String text, int start, int end, @Nullable Calendar referenceTime) {
final String classifiedText = text.substring(start, end);
final TextClassification.Builder builder = new TextClassification.Builder()
.setText(classifiedText);
final int size = classifications.length;
- SmartSelection.ClassificationResult highestScoringResult = null;
+ TextClassifierImplNative.ClassificationResult highestScoringResult = null;
float highestScore = Float.MIN_VALUE;
for (int i = 0; i < size; i++) {
- builder.setEntityType(classifications[i].mCollection, classifications[i].mScore);
- if (classifications[i].mScore > highestScore) {
+ builder.setEntityType(classifications[i].getCollection(),
+ classifications[i].getScore());
+ if (classifications[i].getScore() > highestScore) {
highestScoringResult = classifications[i];
- highestScore = classifications[i].mScore;
+ highestScore = classifications[i].getScore();
}
}
@@ -433,19 +473,6 @@
}
}
- private static int getHintFlags(CharSequence text, int start, int end) {
- int flag = 0;
- final CharSequence subText = text.subSequence(start, end);
- if (Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(subText).matches()) {
- flag |= SmartSelection.HINT_FLAG_EMAIL;
- }
- if (Patterns.AUTOLINK_WEB_URL.matcher(subText).matches()
- && Linkify.sUrlMatchFilter.acceptMatch(text, start, end)) {
- flag |= SmartSelection.HINT_FLAG_URL;
- }
- return flag;
- }
-
/**
* Closes the ParcelFileDescriptor and logs any errors that occur.
*/
@@ -473,8 +500,9 @@
try {
final ParcelFileDescriptor modelFd = ParcelFileDescriptor.open(
file, ParcelFileDescriptor.MODE_READ_ONLY);
- final int version = SmartSelection.getVersion(modelFd.getFd());
- final String supportedLocalesStr = SmartSelection.getLanguages(modelFd.getFd());
+ final int version = TextClassifierImplNative.getVersion(modelFd.getFd());
+ final String supportedLocalesStr =
+ TextClassifierImplNative.getLocales(modelFd.getFd());
if (supportedLocalesStr.isEmpty()) {
Log.d(DEFAULT_LOG_TAG, "Ignoring " + file.getAbsolutePath());
return null;
@@ -560,9 +588,9 @@
public static List<Intent> create(
Context context,
@Nullable Calendar referenceTime,
- SmartSelection.ClassificationResult classification,
+ TextClassifierImplNative.ClassificationResult classification,
String text) {
- final String type = classification.mCollection.trim().toLowerCase(Locale.ENGLISH);
+ final String type = classification.getCollection().trim().toLowerCase(Locale.ENGLISH);
text = text.trim();
switch (type) {
case TextClassifier.TYPE_EMAIL:
@@ -575,9 +603,10 @@
return createForUrl(context, text);
case TextClassifier.TYPE_DATE:
case TextClassifier.TYPE_DATE_TIME:
- if (classification.mDatetime != null) {
+ if (classification.getDatetimeResult() != null) {
Calendar eventTime = Calendar.getInstance();
- eventTime.setTimeInMillis(classification.mDatetime.mMsSinceEpoch);
+ eventTime.setTimeInMillis(
+ classification.getDatetimeResult().getTimeMsUtc());
return createForDatetime(type, referenceTime, eventTime);
} else {
return new ArrayList<>();
diff --git a/core/java/android/view/textclassifier/TextClassifierImplNative.java b/core/java/android/view/textclassifier/TextClassifierImplNative.java
new file mode 100644
index 0000000..3d4c8f2
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifierImplNative.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.content.res.AssetFileDescriptor;
+
+/**
+ * Java wrapper for TextClassifier native library interface. This library is used for detecting
+ * entities in text.
+ */
+final class TextClassifierImplNative {
+
+ static {
+ System.loadLibrary("textclassifier");
+ }
+
+ private final long mModelPtr;
+
+ /**
+ * Creates a new instance of TextClassifierImplNative, using the provided model image, given as
+ * a file descriptor.
+ */
+ TextClassifierImplNative(int fd) {
+ mModelPtr = nativeNew(fd);
+ if (mModelPtr == 0L) {
+ throw new IllegalArgumentException("Couldn't initialize TC from file descriptor.");
+ }
+ }
+
+ /**
+ * Creates a new instance of TextClassifierImplNative, using the provided model image, given as
+ * a file path.
+ */
+ TextClassifierImplNative(String path) {
+ mModelPtr = nativeNewFromPath(path);
+ if (mModelPtr == 0L) {
+ throw new IllegalArgumentException("Couldn't initialize TC from given file.");
+ }
+ }
+
+ /**
+ * Creates a new instance of TextClassifierImplNative, using the provided model image, given as
+ * an AssetFileDescriptor.
+ */
+ TextClassifierImplNative(AssetFileDescriptor afd) {
+ mModelPtr = nativeNewFromAssetFileDescriptor(afd, afd.getStartOffset(), afd.getLength());
+ if (mModelPtr == 0L) {
+ throw new IllegalArgumentException(
+ "Couldn't initialize TC from given AssetFileDescriptor");
+ }
+ }
+
+ /**
+ * Given a string context and current selection, computes the SmartSelection suggestion.
+ *
+ * <p>The begin and end are character indices into the context UTF8 string. selectionBegin is
+ * the character index where the selection begins, and selectionEnd is the index of one
+ * character past the selection span.
+ *
+ * <p>The return value is an array of two ints: suggested selection beginning and end, with the
+ * same semantics as the input selectionBeginning and selectionEnd.
+ */
+ public int[] suggestSelection(
+ String context, int selectionBegin, int selectionEnd, SelectionOptions options) {
+ return nativeSuggestSelection(mModelPtr, context, selectionBegin, selectionEnd, options);
+ }
+
+ /**
+ * Given a string context and current selection, classifies the type of the selected text.
+ *
+ * <p>The begin and end params are character indices in the context string.
+ *
+ * <p>Returns an array of ClassificationResult objects with the probability scores for different
+ * collections.
+ */
+ public ClassificationResult[] classifyText(
+ String context, int selectionBegin, int selectionEnd, ClassificationOptions options) {
+ return nativeClassifyText(mModelPtr, context, selectionBegin, selectionEnd, options);
+ }
+
+ /**
+ * Annotates given input text. The annotations should cover the whole input context except for
+ * whitespaces, and are sorted by their position in the context string.
+ */
+ public AnnotatedSpan[] annotate(String text, AnnotationOptions options) {
+ return nativeAnnotate(mModelPtr, text, options);
+ }
+
+ /** Frees up the allocated memory. */
+ public void close() {
+ nativeClose(mModelPtr);
+ }
+
+ /** Returns a comma separated list of locales supported by the model as BCP 47 tags. */
+ public static String getLocales(int fd) {
+ return nativeGetLocales(fd);
+ }
+
+ /** Returns the version of the model. */
+ public static int getVersion(int fd) {
+ return nativeGetVersion(fd);
+ }
+
+ /** Represents a datetime parsing result from classifyText calls. */
+ public static final class DatetimeResult {
+ static final int GRANULARITY_YEAR = 0;
+ static final int GRANULARITY_MONTH = 1;
+ static final int GRANULARITY_WEEK = 2;
+ static final int GRANULARITY_DAY = 3;
+ static final int GRANULARITY_HOUR = 4;
+ static final int GRANULARITY_MINUTE = 5;
+ static final int GRANULARITY_SECOND = 6;
+
+ private final long mTimeMsUtc;
+ private final int mGranularity;
+
+ DatetimeResult(long timeMsUtc, int granularity) {
+ mGranularity = granularity;
+ mTimeMsUtc = timeMsUtc;
+ }
+
+ public long getTimeMsUtc() {
+ return mTimeMsUtc;
+ }
+
+ public int getGranularity() {
+ return mGranularity;
+ }
+ }
+
+ /** Represents a result of classifyText method call. */
+ public static final class ClassificationResult {
+ private final String mCollection;
+ private final float mScore;
+ private final DatetimeResult mDatetimeResult;
+
+ ClassificationResult(
+ String collection, float score, DatetimeResult datetimeResult) {
+ mCollection = collection;
+ mScore = score;
+ mDatetimeResult = datetimeResult;
+ }
+
+ public String getCollection() {
+ if (mCollection.equals(TextClassifier.TYPE_DATE) && mDatetimeResult != null) {
+ switch (mDatetimeResult.getGranularity()) {
+ case DatetimeResult.GRANULARITY_HOUR:
+ // fall through
+ case DatetimeResult.GRANULARITY_MINUTE:
+ // fall through
+ case DatetimeResult.GRANULARITY_SECOND:
+ return TextClassifier.TYPE_DATE_TIME;
+ default:
+ return TextClassifier.TYPE_DATE;
+ }
+ }
+ return mCollection;
+ }
+
+ public float getScore() {
+ return mScore;
+ }
+
+ public DatetimeResult getDatetimeResult() {
+ return mDatetimeResult;
+ }
+ }
+
+ /** Represents a result of Annotate call. */
+ public static final class AnnotatedSpan {
+ private final int mStartIndex;
+ private final int mEndIndex;
+ private final ClassificationResult[] mClassification;
+
+ AnnotatedSpan(
+ int startIndex, int endIndex, ClassificationResult[] classification) {
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mClassification = classification;
+ }
+
+ public int getStartIndex() {
+ return mStartIndex;
+ }
+
+ public int getEndIndex() {
+ return mEndIndex;
+ }
+
+ public ClassificationResult[] getClassification() {
+ return mClassification;
+ }
+ }
+
+ /** Represents options for the suggestSelection call. */
+ public static final class SelectionOptions {
+ private final String mLocales;
+
+ SelectionOptions(String locales) {
+ mLocales = locales;
+ }
+
+ public String getLocales() {
+ return mLocales;
+ }
+ }
+
+ /** Represents options for the classifyText call. */
+ public static final class ClassificationOptions {
+ private final long mReferenceTimeMsUtc;
+ private final String mReferenceTimezone;
+ private final String mLocales;
+
+ ClassificationOptions(long referenceTimeMsUtc, String referenceTimezone, String locale) {
+ mReferenceTimeMsUtc = referenceTimeMsUtc;
+ mReferenceTimezone = referenceTimezone;
+ mLocales = locale;
+ }
+
+ public long getReferenceTimeMsUtc() {
+ return mReferenceTimeMsUtc;
+ }
+
+ public String getReferenceTimezone() {
+ return mReferenceTimezone;
+ }
+
+ public String getLocale() {
+ return mLocales;
+ }
+ }
+
+ /** Represents options for the Annotate call. */
+ public static final class AnnotationOptions {
+ private final long mReferenceTimeMsUtc;
+ private final String mReferenceTimezone;
+ private final String mLocales;
+
+ AnnotationOptions(long referenceTimeMsUtc, String referenceTimezone, String locale) {
+ mReferenceTimeMsUtc = referenceTimeMsUtc;
+ mReferenceTimezone = referenceTimezone;
+ mLocales = locale;
+ }
+
+ public long getReferenceTimeMsUtc() {
+ return mReferenceTimeMsUtc;
+ }
+
+ public String getReferenceTimezone() {
+ return mReferenceTimezone;
+ }
+
+ public String getLocale() {
+ return mLocales;
+ }
+ }
+
+ private static native long nativeNew(int fd);
+
+ private static native long nativeNewFromPath(String path);
+
+ private static native long nativeNewFromAssetFileDescriptor(
+ AssetFileDescriptor afd, long offset, long size);
+
+ private static native int[] nativeSuggestSelection(
+ long context,
+ String text,
+ int selectionBegin,
+ int selectionEnd,
+ SelectionOptions options);
+
+ private static native ClassificationResult[] nativeClassifyText(
+ long context,
+ String text,
+ int selectionBegin,
+ int selectionEnd,
+ ClassificationOptions options);
+
+ private static native AnnotatedSpan[] nativeAnnotate(
+ long context, String text, AnnotationOptions options);
+
+ private static native void nativeClose(long context);
+
+ private static native String nativeGetLocales(int fd);
+
+ private static native int nativeGetVersion(int fd);
+}
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index dc24d0c..884cbe8 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -305,6 +305,8 @@
private @ApplyStrategy int mApplyStrategy;
private Function<TextLink, TextLinkSpan> mSpanFactory;
+ private String mCallingPackageName;
+
/**
* Returns a new options object based on the specified link mask.
*/
@@ -377,6 +379,15 @@
}
/**
+ * Sets the name of the package that requested the links to get generated.
+ * @hide
+ */
+ public Options setCallingPackageName(@Nullable String callingPackageName) {
+ mCallingPackageName = callingPackageName;
+ return this;
+ }
+
+ /**
* @return ordered list of locale preferences that can be used to disambiguate
* the provided text
*/
@@ -417,6 +428,16 @@
return mSpanFactory;
}
+ /**
+ * @return the name of the package that requested the links to get generated.
+ * TODO: make available as system API
+ * @hide
+ */
+ @Nullable
+ public String getCallingPackageName() {
+ return mCallingPackageName;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -433,6 +454,7 @@
mEntityConfig.writeToParcel(dest, flags);
}
dest.writeInt(mApplyStrategy);
+ dest.writeString(mCallingPackageName);
}
public static final Parcelable.Creator<Options> CREATOR =
@@ -456,6 +478,7 @@
mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in);
}
mApplyStrategy = in.readInt();
+ mCallingPackageName = in.readString();
}
}
diff --git a/core/java/android/view/textclassifier/logging/GenerateLinksLogger.java b/core/java/android/view/textclassifier/logging/GenerateLinksLogger.java
new file mode 100644
index 0000000..fb6f205
--- /dev/null
+++ b/core/java/android/view/textclassifier/logging/GenerateLinksLogger.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.logging;
+
+import android.annotation.Nullable;
+import android.metrics.LogMaker;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.Preconditions;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * A helper for logging calls to generateLinks.
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class GenerateLinksLogger {
+
+ private static final String LOG_TAG = "GenerateLinksLogger";
+ private static final String ZERO = "0";
+
+ private final MetricsLogger mMetricsLogger;
+ private final Random mRng;
+ private final int mSampleRate;
+
+ /**
+ * @param sampleRate the rate at which log events are written. (e.g. 100 means there is a 0.01
+ * chance that a call to logGenerateLinks results in an event being written).
+ * To write all events, pass 1.
+ */
+ public GenerateLinksLogger(int sampleRate) {
+ mSampleRate = sampleRate;
+ mRng = new Random(System.nanoTime());
+ mMetricsLogger = new MetricsLogger();
+ }
+
+ @VisibleForTesting
+ public GenerateLinksLogger(int sampleRate, MetricsLogger metricsLogger) {
+ mSampleRate = sampleRate;
+ mRng = new Random(System.nanoTime());
+ mMetricsLogger = metricsLogger;
+ }
+
+ /** Logs statistics about a call to generateLinks. */
+ public void logGenerateLinks(CharSequence text, TextLinks links, String callingPackageName,
+ long latencyMs) {
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(links);
+ Preconditions.checkNotNull(callingPackageName);
+ if (!shouldLog()) {
+ return;
+ }
+
+ // Always populate the total stats, and per-entity stats for each entity type detected.
+ final LinkifyStats totalStats = new LinkifyStats();
+ final Map<String, LinkifyStats> perEntityTypeStats = new ArrayMap<>();
+ for (TextLinks.TextLink link : links.getLinks()) {
+ if (link.getEntityCount() == 0) continue;
+ final String entityType = link.getEntity(0);
+ if (entityType == null
+ || TextClassifier.TYPE_OTHER.equals(entityType)
+ || TextClassifier.TYPE_UNKNOWN.equals(entityType)) {
+ continue;
+ }
+ totalStats.countLink(link);
+ perEntityTypeStats.computeIfAbsent(entityType, k -> new LinkifyStats()).countLink(link);
+ }
+
+ final String callId = UUID.randomUUID().toString();
+ writeStats(callId, callingPackageName, null, totalStats, text, latencyMs);
+ for (Map.Entry<String, LinkifyStats> entry : perEntityTypeStats.entrySet()) {
+ writeStats(callId, callingPackageName, entry.getKey(), entry.getValue(), text,
+ latencyMs);
+ }
+ }
+
+ /**
+ * Returns whether this particular event should be logged.
+ *
+ * Sampling is used to reduce the amount of logging data generated.
+ **/
+ private boolean shouldLog() {
+ if (mSampleRate <= 1) {
+ return true;
+ } else {
+ return mRng.nextInt(mSampleRate) == 0;
+ }
+ }
+
+ /** Writes a log event for the given stats. */
+ private void writeStats(String callId, String callingPackageName, @Nullable String entityType,
+ LinkifyStats stats, CharSequence text, long latencyMs) {
+ final LogMaker log = new LogMaker(MetricsEvent.TEXT_CLASSIFIER_GENERATE_LINKS)
+ .setPackageName(callingPackageName)
+ .addTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID, callId)
+ .addTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS, stats.mNumLinks)
+ .addTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH, stats.mNumLinksTextLength)
+ .addTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH, text.length())
+ .addTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY, latencyMs);
+ if (entityType != null) {
+ log.addTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE, entityType);
+ }
+ mMetricsLogger.write(log);
+ debugLog(log);
+ }
+
+ private static void debugLog(LogMaker log) {
+ if (!Logger.DEBUG_LOG_ENABLED) return;
+
+ final String callId = Objects.toString(
+ log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID), "");
+ final String entityType = Objects.toString(
+ log.getTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE), "ANY_ENTITY");
+ final int numLinks = Integer.parseInt(
+ Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS), ZERO));
+ final int linkLength = Integer.parseInt(
+ Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH), ZERO));
+ final int textLength = Integer.parseInt(
+ Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH), ZERO));
+ final int latencyMs = Integer.parseInt(
+ Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY), ZERO));
+
+ Log.d(LOG_TAG, String.format("%s:%s %d links (%d/%d chars) %dms %s", callId, entityType,
+ numLinks, linkLength, textLength, latencyMs, log.getPackageName()));
+ }
+
+ /** Helper class for storing per-entity type statistics. */
+ private static final class LinkifyStats {
+ int mNumLinks;
+ int mNumLinksTextLength;
+
+ void countLink(TextLinks.TextLink link) {
+ mNumLinks += 1;
+ mNumLinksTextLength += link.getEnd() - link.getStart();
+ }
+ }
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 65deb3b..a8f6b03 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1689,8 +1689,8 @@
}
/**
- * Sets the list of domains that are exempt from SafeBrowsing checks. The list is
- * global for all the WebViews.
+ * Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks.
+ * The list is global for all the WebViews.
* <p>
* Each rule should take one of these:
* <table>
@@ -1702,15 +1702,18 @@
* </table>
* <p>
* All other rules, including wildcards, are invalid.
+ * <p>
+ * The correct syntax for hosts is defined by <a
+ * href="https://tools.ietf.org/html/rfc3986#section-3.2.2">RFC 3986</a>.
*
- * @param urls the list of URLs
- * @param callback will be called with {@code true} if URLs are successfully added to the
- * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will
- * be run on the UI thread
+ * @param hosts the list of hosts
+ * @param callback will be called with {@code true} if hosts are successfully added to the
+ * whitelist. It will be called with {@code false} if any hosts are malformed. The callback
+ * will be run on the UI thread
*/
- public static void setSafeBrowsingWhitelist(@NonNull List<String> urls,
+ public static void setSafeBrowsingWhitelist(@NonNull List<String> hosts,
@Nullable ValueCallback<Boolean> callback) {
- getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback);
+ getFactory().getStatics().setSafeBrowsingWhitelist(hosts, callback);
}
/**
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 4f7cdab..4ff49ea 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -89,7 +89,7 @@
* {@link android.webkit.WebView#setSafeBrowsingWhitelist(List<String>,
* ValueCallback<Boolean>)}
*/
- void setSafeBrowsingWhitelist(List<String> urls, ValueCallback<Boolean> callback);
+ void setSafeBrowsingWhitelist(List<String> hosts, ValueCallback<Boolean> callback);
/**
* Implement the API method
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 8836561..6427965 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -18,21 +18,32 @@
import android.annotation.FloatRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.annotation.UiThread;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
-import android.view.Gravity;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.view.Display;
+import android.view.DisplayListCanvas;
import android.view.LayoutInflater;
import android.view.PixelCopy;
+import android.view.RenderNode;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceHolder;
+import android.view.SurfaceSession;
import android.view.SurfaceView;
+import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewParent;
import android.view.ViewRootImpl;
@@ -46,32 +57,37 @@
public final class Magnifier {
// Use this to specify that a previous configuration value does not exist.
private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
+ // The callbacks of the pixel copy requests will be invoked on
+ // the Handler of this Thread when the copy is finished.
+ private static final HandlerThread sPixelCopyHandlerThread =
+ new HandlerThread("magnifier pixel copy result handler");
+
// The view to which this magnifier is attached.
private final View mView;
// The coordinates of the view in the surface.
private final int[] mViewCoordinatesInSurface;
// The window containing the magnifier.
- private final PopupWindow mWindow;
+ private InternalPopupWindow mWindow;
// The center coordinates of the window containing the magnifier.
private final Point mWindowCoords = new Point();
// The width of the window containing the magnifier.
private final int mWindowWidth;
// The height of the window containing the magnifier.
private final int mWindowHeight;
- // The bitmap used to display the contents of the magnifier.
- private final Bitmap mBitmap;
+ // The width of the bitmaps where the magnifier content is copied.
+ private final int mBitmapWidth;
+ // The height of the bitmaps where the magnifier content is copied.
+ private final int mBitmapHeight;
+ // The elevation of the window containing the magnifier.
+ private final float mWindowElevation;
// The center coordinates of the content that is to be magnified.
private final Point mCenterZoomCoords = new Point();
- // The callback of the pixel copy request will be invoked on this Handler when
- // the copy is finished.
- private final Handler mPixelCopyHandler = Handler.getMain();
- // Current magnification scale.
- private final float mZoomScale;
// Variables holding previous states, used for detecting redundant calls and invalidation.
private final Point mPrevStartCoordsInSurface = new Point(
NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
private final PointF mPrevPosInView = new PointF(
NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
+ // Rectangle defining the view surface area we pixel copy content from.
private final Rect mPixelCopyRequestRect = new Rect();
/**
@@ -82,8 +98,6 @@
public Magnifier(@NonNull View view) {
mView = Preconditions.checkNotNull(view);
final Context context = mView.getContext();
- final float elevation = context.getResources().getDimension(
- com.android.internal.R.dimen.magnifier_elevation);
final View content = LayoutInflater.from(context).inflate(
com.android.internal.R.layout.magnifier, null);
content.findViewById(com.android.internal.R.id.magnifier_inner).setClipToOutline(true);
@@ -91,23 +105,18 @@
com.android.internal.R.dimen.magnifier_width);
mWindowHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.magnifier_height);
- mZoomScale = context.getResources().getFloat(
+ mWindowElevation = context.getResources().getDimension(
+ com.android.internal.R.dimen.magnifier_elevation);
+ final float zoomScale = context.getResources().getFloat(
com.android.internal.R.dimen.magnifier_zoom_scale);
+ mBitmapWidth = Math.round(mWindowWidth / zoomScale);
+ mBitmapHeight = Math.round(mWindowHeight / zoomScale);
// The view's surface coordinates will not be updated until the magnifier is first shown.
mViewCoordinatesInSurface = new int[2];
+ }
- mWindow = new PopupWindow(context);
- mWindow.setContentView(content);
- mWindow.setWidth(mWindowWidth);
- mWindow.setHeight(mWindowHeight);
- mWindow.setElevation(elevation);
- mWindow.setTouchable(false);
- mWindow.setBackgroundDrawable(null);
-
- final int bitmapWidth = Math.round(mWindowWidth / mZoomScale);
- final int bitmapHeight = Math.round(mWindowHeight / mZoomScale);
- mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
- getImageView().setImageBitmap(mBitmap);
+ static {
+ sPixelCopyHandlerThread.start();
}
/**
@@ -155,30 +164,45 @@
}
final int startX = Math.max(zeroScrollXInSurface, Math.min(
- mCenterZoomCoords.x - mBitmap.getWidth() / 2,
- zeroScrollXInSurface + actualWidth - mBitmap.getWidth()));
- final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+ mCenterZoomCoords.x - mBitmapWidth / 2,
+ zeroScrollXInSurface + actualWidth - mBitmapWidth));
+ final int startY = mCenterZoomCoords.y - mBitmapHeight / 2;
if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
- performPixelCopy(startX, startY);
-
+ if (mWindow == null) {
+ mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(),
+ getValidViewSurface(), mWindowWidth, mWindowHeight, mWindowElevation,
+ Handler.getMain() /* draw the magnifier on the UI thread */, mCallback);
+ }
+ performPixelCopy(startX, startY, true /* update window position */);
mPrevPosInView.x = xPosInView;
mPrevPosInView.y = yPosInView;
-
- if (mWindow.isShowing()) {
- mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
- mWindow.getHeight());
- } else {
- mWindow.showAtLocation(mView, Gravity.NO_GRAVITY, mWindowCoords.x, mWindowCoords.y);
- }
}
}
+ @Nullable
+ private Surface getValidViewSurface() {
+ // TODO: deduplicate this against the first part of #performPixelCopy
+ final Surface surface;
+ if (mView instanceof SurfaceView) {
+ surface = ((SurfaceView) mView).getHolder().getSurface();
+ } else if (mView.getViewRootImpl() != null) {
+ surface = mView.getViewRootImpl().mSurface;
+ } else {
+ surface = null;
+ }
+
+ return (surface != null && surface.isValid()) ? surface : null;
+ }
+
/**
* Dismisses the magnifier from the screen. Calling this on a dismissed magnifier is a no-op.
*/
public void dismiss() {
- mWindow.dismiss();
+ if (mWindow != null) {
+ mWindow.destroy();
+ mWindow = null;
+ }
}
/**
@@ -186,43 +210,40 @@
* {@link #show(float, float)}. This only happens if the magnifier is currently showing.
*/
public void update() {
- if (mWindow.isShowing()) {
- // Update the contents shown in the magnifier.
- performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y);
+ if (mWindow != null) {
+ // Update the content shown in the magnifier.
+ performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y,
+ false /* update window position */);
}
}
private void configureCoordinates(final float xPosInView, final float yPosInView) {
// Compute the coordinates of the center of the content going to be displayed in the
// magnifier. These are relative to the surface the content is copied from.
- final float contentPosX;
- final float contentPosY;
+ final float posX;
+ final float posY;
if (mView instanceof SurfaceView) {
// No offset required if the backing Surface matches the size of the SurfaceView.
- contentPosX = xPosInView;
- contentPosY = yPosInView;
+ posX = xPosInView;
+ posY = yPosInView;
} else {
mView.getLocationInSurface(mViewCoordinatesInSurface);
- contentPosX = xPosInView + mViewCoordinatesInSurface[0];
- contentPosY = yPosInView + mViewCoordinatesInSurface[1];
+ posX = xPosInView + mViewCoordinatesInSurface[0];
+ posY = yPosInView + mViewCoordinatesInSurface[1];
}
- mCenterZoomCoords.x = Math.round(contentPosX);
- mCenterZoomCoords.y = Math.round(contentPosY);
+ mCenterZoomCoords.x = Math.round(posX);
+ mCenterZoomCoords.y = Math.round(posY);
- // Compute the position of the magnifier window. These have to be relative to the window
- // of the view the magnifier is attached to, as the magnifier popup is a panel window
- // attached to that window.
- final int[] viewCoordinatesInWindow = new int[2];
- mView.getLocationInWindow(viewCoordinatesInWindow);
+ // Compute the position of the magnifier window. Again, this has to be relative to the
+ // surface of the magnified view, as this surface is the parent of the magnifier surface.
final int verticalOffset = mView.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.magnifier_offset);
- final float magnifierPosX = xPosInView + viewCoordinatesInWindow[0];
- final float magnifierPosY = yPosInView + viewCoordinatesInWindow[1] - verticalOffset;
- mWindowCoords.x = Math.round(magnifierPosX - mWindowWidth / 2);
- mWindowCoords.y = Math.round(magnifierPosY - mWindowHeight / 2);
+ mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
+ mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalOffset;
}
- private void performPixelCopy(final int startXInSurface, final int startYInSurface) {
+ private void performPixelCopy(final int startXInSurface, final int startYInSurface,
+ final boolean updateWindowPosition) {
// Get the view surface where the content will be copied from.
final Surface surface;
final int surfaceWidth;
@@ -256,20 +277,293 @@
// Perform the pixel copy.
mPixelCopyRequestRect.set(clampedStartXInSurface,
clampedStartYInSurface,
- clampedStartXInSurface + mBitmap.getWidth(),
- clampedStartYInSurface + mBitmap.getHeight());
- PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap,
+ clampedStartXInSurface + mBitmapWidth,
+ clampedStartYInSurface + mBitmapHeight);
+ final int windowCoordsX = mWindowCoords.x;
+ final int windowCoordsY = mWindowCoords.y;
+
+ final Bitmap bitmap =
+ Bitmap.createBitmap(mBitmapWidth, mBitmapHeight, Bitmap.Config.ARGB_8888);
+ PixelCopy.request(surface, mPixelCopyRequestRect, bitmap,
result -> {
- getImageView().invalidate();
- mPrevStartCoordsInSurface.x = startXInSurface;
- mPrevStartCoordsInSurface.y = startYInSurface;
+ synchronized (mWindow.mLock) {
+ if (mWindow != null) {
+ if (updateWindowPosition) {
+ // TODO: pull the position update outside #performPixelCopy
+ mWindow.setContentPositionForNextDraw(windowCoordsX, windowCoordsY);
+ }
+ mWindow.updateContent(bitmap);
+ }
+ }
},
- mPixelCopyHandler);
+ sPixelCopyHandlerThread.getThreadHandler());
+ mPrevStartCoordsInSurface.x = startXInSurface;
+ mPrevStartCoordsInSurface.y = startYInSurface;
}
- private ImageView getImageView() {
- return mWindow.getContentView().findViewById(
- com.android.internal.R.id.magnifier_image);
+ /**
+ * Magnifier's own implementation of PopupWindow-similar floating window.
+ * This exists to ensure frame-synchronization between window position updates and window
+ * content updates. By using a PopupWindow, these events would happen in different frames,
+ * producing a shakiness effect for the magnifier content.
+ */
+ private static class InternalPopupWindow {
+ // Display associated to the view the magnifier is attached to.
+ private final Display mDisplay;
+ // The size of the content of the magnifier.
+ private final int mContentWidth;
+ private final int mContentHeight;
+ // The size of the allocated surface.
+ private final int mSurfaceWidth;
+ private final int mSurfaceHeight;
+ // The insets of the content inside the allocated surface.
+ private final int mOffsetX;
+ private final int mOffsetY;
+ // The surface we allocate for the magnifier content + shadow.
+ private final SurfaceSession mSurfaceSession;
+ private final SurfaceControl mSurfaceControl;
+ private final Surface mSurface;
+ // The renderer used for the allocated surface.
+ private final ThreadedRenderer.SimpleRenderer mRenderer;
+ // The RenderNode used to draw the magnifier content in the surface.
+ private final RenderNode mBitmapRenderNode;
+ // The job that will be post'd to apply the pending magnifier updates to the surface.
+ private final Runnable mMagnifierUpdater;
+ // The handler where the magnifier updater jobs will be post'd.
+ private final Handler mHandler;
+ // The callback to be run after the next draw. Only used for testing.
+ private Callback mCallback;
+
+ // Members below describe the state of the magnifier. Reads/writes to them
+ // have to be synchronized between the UI thread and the thread that handles
+ // the pixel copy results. This is the purpose of mLock.
+ private final Object mLock = new Object();
+ // Whether a magnifier frame draw is currently pending in the UI thread queue.
+ private boolean mFrameDrawScheduled;
+ // The content bitmap.
+ private Bitmap mBitmap;
+ // Whether the next draw will be the first one for the current instance.
+ private boolean mFirstDraw = true;
+ // The window position in the parent surface. Might be applied during the next draw,
+ // when mPendingWindowPositionUpdate is true.
+ private int mWindowPositionX;
+ private int mWindowPositionY;
+ private boolean mPendingWindowPositionUpdate;
+
+ InternalPopupWindow(final Context context, final Display display,
+ final Surface parentSurface,
+ final int width, final int height, final float elevation,
+ final Handler handler, final Callback callback) {
+ mDisplay = display;
+ mCallback = callback;
+
+ mContentWidth = width;
+ mContentHeight = height;
+ mOffsetX = (int) (0.1f * width);
+ mOffsetY = (int) (0.1f * height);
+ // Setup the surface we will use for drawing the content and shadow.
+ mSurfaceWidth = mContentWidth + 2 * mOffsetX;
+ mSurfaceHeight = mContentHeight + 2 * mOffsetY;
+ mSurfaceSession = new SurfaceSession(parentSurface);
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .setSize(mSurfaceWidth, mSurfaceHeight)
+ .setName("magnifier surface")
+ .setFlags(SurfaceControl.HIDDEN)
+ .build();
+ mSurface = new Surface();
+ mSurface.copyFrom(mSurfaceControl);
+
+ // Setup the RenderNode tree. The root has only one child, which contains the bitmap.
+ mRenderer = new ThreadedRenderer.SimpleRenderer(
+ context,
+ "magnifier renderer",
+ mSurface
+ );
+ mBitmapRenderNode = createRenderNodeForBitmap(
+ "magnifier content",
+ elevation
+ );
+
+ final DisplayListCanvas canvas = mRenderer.getRootNode().start(width, height);
+ try {
+ canvas.insertReorderBarrier();
+ canvas.drawRenderNode(mBitmapRenderNode);
+ canvas.insertInorderBarrier();
+ } finally {
+ mRenderer.getRootNode().end(canvas);
+ }
+
+ // Initialize the update job and the handler where this will be post'd.
+ mHandler = handler;
+ mMagnifierUpdater = this::doDraw;
+ mFrameDrawScheduled = false;
+ }
+
+ private RenderNode createRenderNodeForBitmap(final String name, final float elevation) {
+ final RenderNode bitmapRenderNode = RenderNode.create(name, null);
+
+ // Define the position of the bitmap in the parent render node. The surface regions
+ // outside the bitmap are used to draw elevation.
+ bitmapRenderNode.setLeftTopRightBottom(mOffsetX, mOffsetY,
+ mOffsetX + mContentWidth, mOffsetY + mContentHeight);
+ bitmapRenderNode.setElevation(elevation);
+
+ final Outline outline = new Outline();
+ outline.setRoundRect(0, 0, mContentWidth, mContentHeight, 3);
+ outline.setAlpha(1.0f);
+ bitmapRenderNode.setOutline(outline);
+ bitmapRenderNode.setClipToOutline(true);
+
+ // Create a dummy draw, which will be replaced later with real drawing.
+ final DisplayListCanvas canvas = bitmapRenderNode.start(mContentWidth, mContentHeight);
+ try {
+ canvas.drawColor(0xFF00FF00);
+ } finally {
+ bitmapRenderNode.end(canvas);
+ }
+
+ return bitmapRenderNode;
+ }
+
+ /**
+ * Sets the position of the magnifier content relative to the parent surface.
+ * The position update will happen in the same frame with the next draw.
+ * The method has to be called in a context that holds {@link #mLock}.
+ *
+ * @param contentX the x coordinate of the content
+ * @param contentY the y coordinate of the content
+ */
+ public void setContentPositionForNextDraw(final int contentX, final int contentY) {
+ mWindowPositionX = contentX - mOffsetX;
+ mWindowPositionY = contentY - mOffsetY;
+ mPendingWindowPositionUpdate = true;
+ requestUpdate();
+ }
+
+ /**
+ * Sets the content that should be displayed in the magnifier.
+ * The update happens immediately, and possibly triggers a pending window movement set
+ * by {@link #setContentPositionForNextDraw(int, int)}.
+ * The method has to be called in a context that holds {@link #mLock}.
+ *
+ * @param bitmap the content bitmap
+ */
+ public void updateContent(final @NonNull Bitmap bitmap) {
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ }
+ mBitmap = bitmap;
+ requestUpdate();
+ }
+
+ private void requestUpdate() {
+ if (mFrameDrawScheduled) {
+ return;
+ }
+ final Message request = Message.obtain(mHandler, mMagnifierUpdater);
+ request.setAsynchronous(true);
+ request.sendToTarget();
+ mFrameDrawScheduled = true;
+ }
+
+ /**
+ * Destroys this instance.
+ */
+ public void destroy() {
+ mRenderer.destroy();
+ mSurface.destroy();
+ mSurfaceControl.destroy();
+ mSurfaceSession.kill();
+ mBitmapRenderNode.destroy();
+ synchronized (mLock) {
+ mHandler.removeCallbacks(mMagnifierUpdater);
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ }
+ }
+ }
+
+ private void doDraw() {
+ final ThreadedRenderer.FrameDrawingCallback callback;
+
+ // Draw the current bitmap to the surface, and prepare the callback which updates the
+ // surface position. These have to be in the same synchronized block, in order to
+ // guarantee the consistency between the bitmap content and the surface position.
+ synchronized (mLock) {
+ if (!mSurface.isValid()) {
+ // Probably #destroy() was called for the current instance, so we skip the draw.
+ return;
+ }
+
+ final DisplayListCanvas canvas =
+ mBitmapRenderNode.start(mContentWidth, mContentHeight);
+ try {
+ final Rect srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ final Rect dstRect = new Rect(0, 0, mContentWidth, mContentHeight);
+ final Paint paint = new Paint();
+ paint.setFilterBitmap(true);
+ canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
+ } finally {
+ mBitmapRenderNode.end(canvas);
+ }
+
+ if (mPendingWindowPositionUpdate || mFirstDraw) {
+ // If the window has to be shown or moved, defer this until the next draw.
+ final boolean firstDraw = mFirstDraw;
+ mFirstDraw = false;
+ final boolean updateWindowPosition = mPendingWindowPositionUpdate;
+ mPendingWindowPositionUpdate = false;
+ final int pendingX = mWindowPositionX;
+ final int pendingY = mWindowPositionY;
+
+ callback = frame -> {
+ mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
+ // Show or move the window at the content draw frame.
+ SurfaceControl.openTransaction();
+ mSurfaceControl.deferTransactionUntil(mSurface, frame);
+ if (updateWindowPosition) {
+ mSurfaceControl.setPosition(pendingX, pendingY);
+ }
+ if (firstDraw) {
+ mSurfaceControl.show();
+ }
+ SurfaceControl.closeTransaction();
+ };
+ } else {
+ callback = null;
+ }
+
+ mFrameDrawScheduled = false;
+ }
+
+ mRenderer.draw(callback);
+ if (mCallback != null) {
+ mCallback.onOperationComplete();
+ }
+ }
+ }
+
+ // The rest of the file consists of test APIs.
+
+ /**
+ * See {@link #setOnOperationCompleteCallback(Callback)}.
+ */
+ @TestApi
+ private Callback mCallback;
+
+ /**
+ * Sets a callback which will be invoked at the end of the next
+ * {@link #show(float, float)} or {@link #update()} operation.
+ *
+ * @hide
+ */
+ @TestApi
+ public void setOnOperationCompleteCallback(final Callback callback) {
+ mCallback = callback;
+ if (mWindow != null) {
+ mWindow.mCallback = callback;
+ }
}
/**
@@ -278,8 +572,13 @@
* @hide
*/
@TestApi
- public Bitmap getContent() {
- return mBitmap;
+ public @Nullable Bitmap getContent() {
+ if (mWindow == null) {
+ return null;
+ }
+ synchronized (mWindow.mLock) {
+ return mWindow.mBitmap;
+ }
}
/**
@@ -296,7 +595,7 @@
final int left = mWindowCoords.x + viewLocationOnScreen[0] - viewLocationInSurface[0];
final int top = mWindowCoords.y + viewLocationOnScreen[1] - viewLocationInSurface[1];
- return new Rect(left, top, left + mWindow.getWidth(), top + mWindow.getHeight());
+ return new Rect(left, top, left + mWindowWidth, top + mWindowHeight);
}
/**
@@ -313,4 +612,15 @@
size.y = resources.getDimension(com.android.internal.R.dimen.magnifier_height) / density;
return size;
}
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public interface Callback {
+ /**
+ * Callback called after the drawing for a magnifier update has happened.
+ */
+ void onOperationComplete();
+ }
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 7217def..9553cf5 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -2563,7 +2563,9 @@
public void onViewDetachedFromWindow(View v) {
v.removeOnAttachStateChangeListener(this);
- TransitionManager.endTransitions(PopupDecorView.this);
+ if (isAttachedToWindow()) {
+ TransitionManager.endTransitions(PopupDecorView.this);
+ }
}
};
diff --git a/core/java/com/android/internal/app/procstats/SparseMappingTable.java b/core/java/com/android/internal/app/procstats/SparseMappingTable.java
index 956ce99..91b2054 100644
--- a/core/java/com/android/internal/app/procstats/SparseMappingTable.java
+++ b/core/java/com/android/internal/app/procstats/SparseMappingTable.java
@@ -18,6 +18,7 @@
import android.os.Build;
import android.os.Parcel;
+import android.util.EventLog;
import android.util.Slog;
import libcore.util.EmptyArray;
@@ -529,6 +530,12 @@
readCompactedLongArray(in, array, size);
mLongs.add(array);
}
+ // Verify that last array's length is consistent with writeToParcel
+ if (N > 0 && mLongs.get(N - 1).length != mNextIndex) {
+ EventLog.writeEvent(0x534e4554, "73252178", -1, "");
+ throw new IllegalStateException("Expected array of length " + mNextIndex + " but was "
+ + mLongs.get(N - 1).length);
+ }
}
/**
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index d1932cf..9b4ea33 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -110,7 +110,10 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
@@ -12334,7 +12337,7 @@
@VisibleForTesting
public void readKernelUidCpuActiveTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesUs) -> {
+ mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
mKernelUidCpuActiveTimeReader.removeUid(uid);
@@ -12347,7 +12350,7 @@
return;
}
final Uid u = getUidStatsLocked(uid);
- u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesUs, onBattery);
+ u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesMs, onBattery);
});
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
@@ -12363,7 +12366,7 @@
@VisibleForTesting
public void readKernelUidCpuClusterTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
- mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesUs) -> {
+ mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
mKernelUidCpuClusterTimeReader.removeUid(uid);
@@ -12376,7 +12379,7 @@
return;
}
final Uid u = getUidStatsLocked(uid);
- u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesUs, onBattery);
+ u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesMs, onBattery);
});
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
@@ -13326,17 +13329,20 @@
= "read_binary_cpu_time";
public static final String KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS
= "proc_state_cpu_times_read_delay_ms";
+ public static final String KEY_KERNEL_UID_READERS_THROTTLE_TIME
+ = "kernel_uid_readers_throttle_time";
private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = true;
private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
private static final boolean DEFAULT_READ_BINARY_CPU_TIME = false;
private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000;
+ private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 10_000;
public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
- // Not used right now.
public boolean READ_BINARY_CPU_TIME = DEFAULT_READ_BINARY_CPU_TIME;
public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
+ public long KERNEL_UID_READERS_THROTTLE_TIME = DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -13374,11 +13380,14 @@
DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE));
TRACK_CPU_ACTIVE_CLUSTER_TIME = mParser.getBoolean(
KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME, DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME);
- READ_BINARY_CPU_TIME = mParser.getBoolean(
- KEY_READ_BINARY_CPU_TIME, DEFAULT_READ_BINARY_CPU_TIME);
+ updateReadBinaryCpuTime(READ_BINARY_CPU_TIME,
+ mParser.getBoolean(KEY_READ_BINARY_CPU_TIME, DEFAULT_READ_BINARY_CPU_TIME));
updateProcStateCpuTimesReadDelayMs(PROC_STATE_CPU_TIMES_READ_DELAY_MS,
mParser.getLong(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS,
- DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS));
+ DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS));
+ updateKernelUidReadersThrottleTime(KERNEL_UID_READERS_THROTTLE_TIME,
+ mParser.getLong(KEY_KERNEL_UID_READERS_THROTTLE_TIME,
+ DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME));
}
}
@@ -13394,6 +13403,13 @@
}
}
+ private void updateReadBinaryCpuTime(boolean oldEnabled, boolean isEnabled) {
+ READ_BINARY_CPU_TIME = isEnabled;
+ if (oldEnabled != isEnabled) {
+ mKernelUidCpuFreqTimeReader.setReadBinary(isEnabled);
+ }
+ }
+
private void updateProcStateCpuTimesReadDelayMs(long oldDelayMillis, long newDelayMillis) {
PROC_STATE_CPU_TIMES_READ_DELAY_MS = newDelayMillis;
if (oldDelayMillis != newDelayMillis) {
@@ -13403,6 +13419,16 @@
}
}
+ private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
+ KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
+ if (oldTimeMs != newTimeMs) {
+ mKernelUidCpuFreqTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
+ mKernelUidCpuActiveTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
+ mKernelUidCpuClusterTimeReader
+ .setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME);
+ }
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("=");
pw.println(TRACK_CPU_TIMES_BY_PROC_STATE);
@@ -13412,6 +13438,8 @@
pw.println(READ_BINARY_CPU_TIME);
pw.print(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS); pw.print("=");
pw.println(PROC_STATE_CPU_TIMES_READ_DELAY_MS);
+ pw.print(KEY_KERNEL_UID_READERS_THROTTLE_TIME); pw.print("=");
+ pw.println(KERNEL_UID_READERS_THROTTLE_TIME);
}
}
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index a34e7f5..101c321 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -50,13 +50,14 @@
cpuPowerMaUs += cpuSpeedStepPower;
}
}
- cpuPowerMaUs += u.getCpuActiveTime() * mProfile.getAveragePower(
+ cpuPowerMaUs += u.getCpuActiveTime() * 1000 * mProfile.getAveragePower(
PowerProfile.POWER_CPU_ACTIVE);
long[] cpuClusterTimes = u.getCpuClusterTimes();
if (cpuClusterTimes != null) {
if (cpuClusterTimes.length == numClusters) {
for (int i = 0; i < numClusters; i++) {
- double power = cpuClusterTimes[i] * mProfile.getAveragePowerForCpuCluster(i);
+ double power =
+ cpuClusterTimes[i] * 1000 * mProfile.getAveragePowerForCpuCluster(i);
cpuPowerMaUs += power;
if (DEBUG) {
Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + i + " clusterTimeUs="
diff --git a/core/java/com/android/internal/os/KernelCpuProcReader.java b/core/java/com/android/internal/os/KernelCpuProcReader.java
new file mode 100644
index 0000000..4d56905
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuProcReader.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+
+/**
+ * Reads cpu time proc files with throttling (adjustable interval).
+ *
+ * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance()
+ * method will return corresponding reader instance. In order to prevent frequent GC,
+ * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files.
+ *
+ * A KernelCpuProcReader instance keeps an error counter. When the number of read errors within that
+ * instance accumulates to 5, this instance will reject all further read requests.
+ *
+ * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via
+ * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current
+ * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from
+ * the last read timestamp, {@link #readBytes()} will return previous result.
+ *
+ * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while
+ * accessing its instance methods or digesting the return values.
+ */
+public class KernelCpuProcReader {
+ private static final String TAG = "KernelCpuProcReader";
+ private static final int ERROR_THRESHOLD = 5;
+ // Throttle interval in milliseconds
+ private static final long DEFAULT_THROTTLE_INTERVAL = 3000L;
+ private static final int INITIAL_BUFFER_SIZE = 8 * 1024;
+ private static final int MAX_BUFFER_SIZE = 1024 * 1024;
+ private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state";
+ private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time";
+ private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time";
+
+ private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader(
+ PROC_UID_FREQ_TIME);
+ private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader(
+ PROC_UID_ACTIVE_TIME);
+ private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader(
+ PROC_UID_CLUSTER_TIME);
+
+ public static KernelCpuProcReader getFreqTimeReaderInstance() {
+ return mFreqTimeReader;
+ }
+
+ public static KernelCpuProcReader getActiveTimeReaderInstance() {
+ return mActiveTimeReader;
+ }
+
+ public static KernelCpuProcReader getClusterTimeReaderInstance() {
+ return mClusterTimeReader;
+ }
+
+ private int mErrors;
+ private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
+ private long mLastReadTime = Long.MIN_VALUE;
+ private final Path mProc;
+ private ByteBuffer mBuffer;
+
+ @VisibleForTesting
+ public KernelCpuProcReader(String procFile) {
+ mProc = Paths.get(procFile);
+ mBuffer = ByteBuffer.allocateDirect(INITIAL_BUFFER_SIZE);
+ mBuffer.clear();
+ }
+
+ /**
+ * Reads all bytes from the corresponding proc file.
+ *
+ * If elapsed time since last call to this method is less than the throttle interval, it will
+ * return previous result. When IOException accumulates to 5, it will always return null. This
+ * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this
+ * object while calling this method and digesting its return value.
+ *
+ * @return a {@link ByteBuffer} containing all bytes from the proc file.
+ */
+ public ByteBuffer readBytes() {
+ if (mErrors >= ERROR_THRESHOLD) {
+ return null;
+ }
+ if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) {
+ if (mBuffer.limit() > 0 && mBuffer.limit() < mBuffer.capacity()) {
+ // mBuffer has data.
+ return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
+ }
+ return null;
+ }
+ mLastReadTime = SystemClock.elapsedRealtime();
+ mBuffer.clear();
+ final int oldMask = StrictMode.allowThreadDiskReadsMask();
+ try (FileChannel fc = FileChannel.open(mProc, StandardOpenOption.READ)) {
+ while (fc.read(mBuffer) == mBuffer.capacity()) {
+ if (!resize()) {
+ mErrors++;
+ Slog.e(TAG, "Proc file is too large: " + mProc);
+ return null;
+ }
+ fc.position(0);
+ }
+ } catch (IOException e) {
+ mErrors++;
+ Slog.e(TAG, "Error reading: " + mProc, e);
+ return null;
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
+ mBuffer.flip();
+ return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
+ }
+
+ /**
+ * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock
+ * on this object is recommended.
+ *
+ * @param throttleInterval throttle interval in milliseconds
+ */
+ public void setThrottleInterval(long throttleInterval) {
+ if (throttleInterval >= 0) {
+ mThrottleInterval = throttleInterval;
+ }
+ }
+
+ private boolean resize() {
+ if (mBuffer.capacity() >= MAX_BUFFER_SIZE) {
+ return false;
+ }
+ int newSize = Math.min(mBuffer.capacity() << 1, MAX_BUFFER_SIZE);
+ // Slog.i(TAG, "Resize buffer " + mBuffer.capacity() + " => " + newSize);
+ mBuffer = ByteBuffer.allocateDirect(newSize);
+ return true;
+ }
+}
diff --git a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
index cb96c5c..2519412 100644
--- a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java
@@ -17,53 +17,121 @@
package com.android.internal.os;
import android.annotation.Nullable;
-import android.os.StrictMode;
import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
/**
- * Reads /proc/uid_concurrent_active_time which has the format:
- * active: X (X is # cores)
- * [uid0]: [time-0] [time-1] [time-2] ... (# entries = # cores)
- * [uid1]: [time-0] [time-1] [time-2] ... ...
+ * Reads binary proc file /proc/uid_cpupower/concurrent_active_time and reports CPU active time to
+ * BatteryStats to compute {@link PowerProfile#POWER_CPU_ACTIVE}.
+ *
+ * concurrent_active_time is an array of u32's in the following format:
+ * [n, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the total number of cpus (num_possible_cpus)
* ...
- * Time-N means the CPU time a UID spent running concurrently with N other processes.
+ * timeXn means the CPU time that a UID X spent running concurrently with n other processes.
* The file contains a monotonically increasing count of time for a single boot. This class
* maintains the previous results of a call to {@link #readDelta} in order to provide a
* proper delta.
+ *
+ * This class uses a throttler to reject any {@link #readDelta} call within
+ * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
+ * which has a shorter throttle interval and returns cached result from last read when the request
+ * is throttled.
+ *
+ * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
+ * the nature of {@link #readDelta(Callback)}).
*/
public class KernelUidCpuActiveTimeReader {
- private static final boolean DEBUG = false;
private static final String TAG = "KernelUidCpuActiveTimeReader";
- private static final String UID_TIMES_PROC_FILE = "/proc/uid_concurrent_active_time";
+ // Throttle interval in milliseconds
+ private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
- private int mCoreCount;
- private long mLastTimeReadMs;
- private long mNowTimeMs;
- private SparseArray<long[]> mLastUidCpuActiveTimeMs = new SparseArray<>();
+ private final KernelCpuProcReader mProcReader;
+ private long mLastTimeReadMs = Long.MIN_VALUE;
+ private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
+ private SparseArray<Double> mLastUidCpuActiveTimeMs = new SparseArray<>();
public interface Callback {
+ /**
+ * Notifies when new data is available.
+ *
+ * @param uid uid int
+ * @param cpuActiveTimeMs cpu active time spent by this uid in milliseconds
+ */
void onUidCpuActiveTime(int uid, long cpuActiveTimeMs);
}
+ public KernelUidCpuActiveTimeReader() {
+ mProcReader = KernelCpuProcReader.getActiveTimeReaderInstance();
+ }
+
+ @VisibleForTesting
+ public KernelUidCpuActiveTimeReader(KernelCpuProcReader procReader) {
+ mProcReader = procReader;
+ }
+
public void readDelta(@Nullable Callback cb) {
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- mNowTimeMs = SystemClock.elapsedRealtime();
- readDeltaInternal(reader, cb);
- mLastTimeReadMs = mNowTimeMs;
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
+ if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
+ Slog.w(TAG, "Throttle");
+ return;
+ }
+ synchronized (mProcReader) {
+ final ByteBuffer bytes = mProcReader.readBytes();
+ if (bytes == null || bytes.remaining() <= 4) {
+ // Error already logged in mProcReader.
+ return;
+ }
+ if ((bytes.remaining() & 3) != 0) {
+ Slog.wtf(TAG,
+ "Cannot parse active time proc bytes to int: " + bytes.remaining());
+ return;
+ }
+ final IntBuffer buf = bytes.asIntBuffer();
+ final int cores = buf.get();
+ if (cores <= 0 || buf.remaining() % (cores + 1) != 0) {
+ Slog.wtf(TAG,
+ "Cpu active time format error: " + buf.remaining() + " / " + (cores
+ + 1));
+ return;
+ }
+ int numUids = buf.remaining() / (cores + 1);
+ for (int i = 0; i < numUids; i++) {
+ int uid = buf.get();
+ boolean corrupted = false;
+ double curTime = 0;
+ for (int j = 1; j <= cores; j++) {
+ int time = buf.get();
+ if (time < 0) {
+ Slog.e(TAG, "Corrupted data from active time proc: " + time);
+ corrupted = true;
+ } else {
+ curTime += (double) time * 10 / j; // Unit is 10ms.
+ }
+ }
+ double delta = curTime - mLastUidCpuActiveTimeMs.get(uid, 0.0);
+ if (delta > 0 && !corrupted) {
+ mLastUidCpuActiveTimeMs.put(uid, curTime);
+ if (cb != null) {
+ cb.onUidCpuActiveTime(uid, (long) delta);
+ }
+ }
+ }
+ // Slog.i(TAG, "Read uids: " + numUids);
+ }
+ mLastTimeReadMs = SystemClock.elapsedRealtime();
+ }
+
+ public void setThrottleInterval(long throttleInterval) {
+ if (throttleInterval >= 0) {
+ mThrottleInterval = throttleInterval;
}
}
@@ -82,65 +150,4 @@
final int lastIndex = mLastUidCpuActiveTimeMs.indexOfKey(endUid);
mLastUidCpuActiveTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
}
-
- @VisibleForTesting
- public void readDeltaInternal(BufferedReader reader, @Nullable Callback cb) throws IOException {
- String line = reader.readLine();
- if (line == null || !line.startsWith("active:")) {
- Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE));
- return;
- }
- if (mCoreCount == 0) {
- mCoreCount = Integer.parseInt(line.substring(line.indexOf(' ')+1));
- }
- while ((line = reader.readLine()) != null) {
- final int index = line.indexOf(' ');
- final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
- readTimesForUid(uid, line.substring(index + 1), cb);
- }
- }
-
- private void readTimesForUid(int uid, String line, @Nullable Callback cb) {
- long[] lastActiveTime = mLastUidCpuActiveTimeMs.get(uid);
- if (lastActiveTime == null) {
- lastActiveTime = new long[mCoreCount];
- mLastUidCpuActiveTimeMs.put(uid, lastActiveTime);
- }
- final String[] timesStr = line.split(" ");
- if (timesStr.length != mCoreCount) {
- Slog.e(TAG, String.format("# readings don't match # cores, readings: %d, CPU cores: %d",
- timesStr.length, mCoreCount));
- return;
- }
- long sumDeltas = 0;
- final long[] curActiveTime = new long[mCoreCount];
- boolean notify = false;
- for (int i = 0; i < mCoreCount; i++) {
- // Times read will be in units of 10ms
- curActiveTime[i] = Long.parseLong(timesStr[i], 10) * 10;
- long delta = curActiveTime[i] - lastActiveTime[i];
- if (delta < 0 || curActiveTime[i] < 0) {
- if (DEBUG) {
- final StringBuilder sb = new StringBuilder();
- sb.append(String.format("Malformed cpu active time for UID=%d\n", uid));
- sb.append(String.format("data=(%d,%d)\n", lastActiveTime[i], curActiveTime[i]));
- sb.append("times=(");
- TimeUtils.formatDuration(mLastTimeReadMs, sb);
- sb.append(",");
- TimeUtils.formatDuration(mNowTimeMs, sb);
- sb.append(")");
- Slog.e(TAG, sb.toString());
- }
- return;
- }
- notify |= delta > 0;
- sumDeltas += delta / (i + 1);
- }
- if (notify) {
- System.arraycopy(curActiveTime, 0, lastActiveTime, 0, mCoreCount);
- if (cb != null) {
- cb.onUidCpuActiveTime(uid, sumDeltas);
- }
- }
- }
}
diff --git a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
index 85153bc..41ef8f0 100644
--- a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java
@@ -17,64 +17,191 @@
package com.android.internal.os;
import android.annotation.Nullable;
-import android.os.StrictMode;
import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
/**
- * Reads /proc/uid_concurrent_policy_time which has the format:
- * policy0: X policy4: Y (there are X cores on policy0, Y cores on policy4)
- * [uid0]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ...
- * [uid1]: [time-0-0] [time-0-1] ... [time-1-0] [time-1-1] ...
- * ...
- * Time-X-Y means the time a UID spent on clusterX running concurrently with Y other processes.
+ * Reads binary proc file /proc/uid_cpupower/concurrent_policy_time and reports CPU cluster times
+ * to BatteryStats to compute cluster power. See
+ * {@link PowerProfile#getAveragePowerForCpuCluster(int)}.
+ *
+ * concurrent_policy_time is an array of u32's in the following format:
+ * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the number of policies
+ * xi is the number cpus on a particular policy
+ * Each uidX is followed by x0 time entries corresponding to the time UID X spent on cluster0
+ * running concurrently with 0, 1, 2, ..., x0 - 1 other processes, then followed by x1, ..., xn
+ * time entries.
+ *
* The file contains a monotonically increasing count of time for a single boot. This class
- * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
- * delta.
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a
+ * proper delta.
+ *
+ * This class uses a throttler to reject any {@link #readDelta} call within
+ * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
+ * which has a shorter throttle interval and returns cached result from last read when the request
+ * is throttled.
+ *
+ * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
+ * the nature of {@link #readDelta(Callback)}).
*/
public class KernelUidCpuClusterTimeReader {
-
- private static final boolean DEBUG = false;
private static final String TAG = "KernelUidCpuClusterTimeReader";
- private static final String UID_TIMES_PROC_FILE = "/proc/uid_concurrent_policy_time";
+ // Throttle interval in milliseconds
+ private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
- // mCoreOnCluster[i] is the # of cores on cluster i
- private int[] mCoreOnCluster;
- private int mCores;
- private long mLastTimeReadMs;
- private long mNowTimeMs;
- private SparseArray<long[]> mLastUidPolicyTimeMs = new SparseArray<>();
+ private final KernelCpuProcReader mProcReader;
+ private long mLastTimeReadMs = Long.MIN_VALUE;
+ private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
+ private SparseArray<double[]> mLastUidPolicyTimeMs = new SparseArray<>();
+
+ private int mNumClusters = -1;
+ private int mNumCores;
+ private int[] mNumCoresOnCluster;
+
+ private double[] mCurTime; // Reuse to avoid GC.
+ private long[] mDeltaTime; // Reuse to avoid GC.
public interface Callback {
/**
- * @param uid
- * @param cpuActiveTimeMs the first dimension is cluster, the second dimension is the # of
- * processes running concurrently with this uid.
+ * Notifies when new data is available.
+ *
+ * @param uid uid int
+ * @param cpuClusterTimeMs an array of times spent by this uid on corresponding clusters.
+ * The array index is the cluster index.
*/
- void onUidCpuPolicyTime(int uid, long[] cpuActiveTimeMs);
+ void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs);
+ }
+
+ public KernelUidCpuClusterTimeReader() {
+ mProcReader = KernelCpuProcReader.getClusterTimeReaderInstance();
+ }
+
+ @VisibleForTesting
+ public KernelUidCpuClusterTimeReader(KernelCpuProcReader procReader) {
+ mProcReader = procReader;
+ }
+
+ public void setThrottleInterval(long throttleInterval) {
+ if (throttleInterval >= 0) {
+ mThrottleInterval = throttleInterval;
+ }
}
public void readDelta(@Nullable Callback cb) {
- final int oldMask = StrictMode.allowThreadDiskReadsMask();
- try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- mNowTimeMs = SystemClock.elapsedRealtime();
- readDeltaInternal(reader, cb);
- mLastTimeReadMs = mNowTimeMs;
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
- } finally {
- StrictMode.setThreadPolicyMask(oldMask);
+ if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
+ Slog.w(TAG, "Throttle");
+ return;
}
+ synchronized (mProcReader) {
+ ByteBuffer bytes = mProcReader.readBytes();
+ if (bytes == null || bytes.remaining() <= 4) {
+ // Error already logged in mProcReader.
+ return;
+ }
+ if ((bytes.remaining() & 3) != 0) {
+ Slog.wtf(TAG,
+ "Cannot parse cluster time proc bytes to int: " + bytes.remaining());
+ return;
+ }
+ IntBuffer buf = bytes.asIntBuffer();
+ final int numClusters = buf.get();
+ if (numClusters <= 0) {
+ Slog.wtf(TAG, "Cluster time format error: " + numClusters);
+ return;
+ }
+ if (mNumClusters == -1) {
+ mNumClusters = numClusters;
+ }
+ if (buf.remaining() < numClusters) {
+ Slog.wtf(TAG, "Too few data left in the buffer: " + buf.remaining());
+ return;
+ }
+ if (mNumCores <= 0) {
+ if (!readCoreInfo(buf, numClusters)) {
+ return;
+ }
+ } else {
+ buf.position(buf.position() + numClusters);
+ }
+
+ if (buf.remaining() % (mNumCores + 1) != 0) {
+ Slog.wtf(TAG,
+ "Cluster time format error: " + buf.remaining() + " / " + (mNumCores
+ + 1));
+ return;
+ }
+ int numUids = buf.remaining() / (mNumCores + 1);
+
+ for (int i = 0; i < numUids; i++) {
+ processUidLocked(buf, cb);
+ }
+ // Slog.i(TAG, "Read uids: " + numUids);
+ }
+ mLastTimeReadMs = SystemClock.elapsedRealtime();
+ }
+
+ private void processUidLocked(IntBuffer buf, @Nullable Callback cb) {
+ int uid = buf.get();
+ double[] lastTimes = mLastUidPolicyTimeMs.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new double[mNumClusters];
+ mLastUidPolicyTimeMs.put(uid, lastTimes);
+ }
+
+ boolean notify = false;
+ boolean corrupted = false;
+
+ for (int j = 0; j < mNumClusters; j++) {
+ mCurTime[j] = 0;
+ for (int k = 1; k <= mNumCoresOnCluster[j]; k++) {
+ int time = buf.get();
+ if (time < 0) {
+ Slog.e(TAG, "Corrupted data from cluster time proc uid: " + uid);
+ corrupted = true;
+ }
+ mCurTime[j] += (double) time * 10 / k; // Unit is 10ms.
+ }
+ mDeltaTime[j] = (long) (mCurTime[j] - lastTimes[j]);
+ if (mDeltaTime[j] < 0) {
+ Slog.e(TAG, "Unexpected delta from cluster time proc uid: " + uid);
+ corrupted = true;
+ }
+ notify |= mDeltaTime[j] > 0;
+ }
+ if (notify && !corrupted) {
+ System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
+ if (cb != null) {
+ cb.onUidCpuPolicyTime(uid, mDeltaTime);
+ }
+ }
+ }
+
+ // Returns if it has read valid info.
+ private boolean readCoreInfo(IntBuffer buf, int numClusters) {
+ int numCores = 0;
+ int[] numCoresOnCluster = new int[numClusters];
+ for (int i = 0; i < numClusters; i++) {
+ numCoresOnCluster[i] = buf.get();
+ numCores += numCoresOnCluster[i];
+ }
+ if (numCores <= 0) {
+ Slog.e(TAG, "Invalid # cores from cluster time proc file: " + numCores);
+ return false;
+ }
+ mNumCores = numCores;
+ mNumCoresOnCluster = numCoresOnCluster;
+ mCurTime = new double[numClusters];
+ mDeltaTime = new long[numClusters];
+ return true;
}
public void removeUid(int uid) {
@@ -92,87 +219,4 @@
final int lastIndex = mLastUidPolicyTimeMs.indexOfKey(endUid);
mLastUidPolicyTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
}
-
- @VisibleForTesting
- public void readDeltaInternal(BufferedReader reader, @Nullable Callback cb) throws IOException {
- String line = reader.readLine();
- if (line == null || !line.startsWith("policy")) {
- Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE));
- return;
- }
- if (mCoreOnCluster == null) {
- List<Integer> list = new ArrayList<>();
- String[] policies = line.split(" ");
-
- if (policies.length == 0 || policies.length % 2 != 0) {
- Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE));
- return;
- }
-
- for (int i = 0; i < policies.length; i+=2) {
- list.add(Integer.parseInt(policies[i+1]));
- }
-
- mCoreOnCluster = new int[list.size()];
- for(int i=0;i<list.size();i++){
- mCoreOnCluster[i] = list.get(i);
- mCores += mCoreOnCluster[i];
- }
- }
- while ((line = reader.readLine()) != null) {
- final int index = line.indexOf(' ');
- final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
- readTimesForUid(uid, line.substring(index + 1), cb);
- }
- }
-
- private void readTimesForUid(int uid, String line, @Nullable Callback cb) {
- long[] lastPolicyTime = mLastUidPolicyTimeMs.get(uid);
- if (lastPolicyTime == null) {
- lastPolicyTime = new long[mCores];
- mLastUidPolicyTimeMs.put(uid, lastPolicyTime);
- }
- final String[] timeStr = line.split(" ");
- if (timeStr.length != mCores) {
- Slog.e(TAG, String.format("# readings don't match # cores, readings: %d, # CPU cores: %d",
- timeStr.length, mCores));
- return;
- }
- final long[] deltaPolicyTime = new long[mCores];
- final long[] currPolicyTime = new long[mCores];
- boolean notify = false;
- for (int i = 0; i < mCores; i++) {
- // Times read will be in units of 10ms
- currPolicyTime[i] = Long.parseLong(timeStr[i], 10) * 10;
- deltaPolicyTime[i] = currPolicyTime[i] - lastPolicyTime[i];
- if (deltaPolicyTime[i] < 0 || currPolicyTime[i] < 0) {
- if (DEBUG) {
- final StringBuilder sb = new StringBuilder();
- sb.append(String.format("Malformed cpu policy time for UID=%d\n", uid));
- sb.append(String.format("data=(%d,%d)\n", lastPolicyTime[i], currPolicyTime[i]));
- sb.append("times=(");
- TimeUtils.formatDuration(mLastTimeReadMs, sb);
- sb.append(",");
- TimeUtils.formatDuration(mNowTimeMs, sb);
- sb.append(")");
- Slog.e(TAG, sb.toString());
- }
- return;
- }
- notify |= deltaPolicyTime[i] > 0;
- }
- if (notify) {
- System.arraycopy(currPolicyTime, 0, lastPolicyTime, 0, mCores);
- if (cb != null) {
- final long[] times = new long[mCoreOnCluster.length];
- int core = 0;
- for (int i = 0; i < mCoreOnCluster.length; i++) {
- for (int j = 0; j < mCoreOnCluster[i]; j++) {
- times[i] += deltaPolicyTime[core++] / (j+1);
- }
- }
- cb.onUidCpuPolicyTime(uid, times);
- }
- }
- }
}
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index d97538c..a21a70e 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -32,6 +32,8 @@
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
/**
* Reads /proc/uid_time_in_state which has the format:
@@ -41,24 +43,45 @@
* [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
* ...
*
+ * Binary variation reads /proc/uid_cpupower/time_in_state in the following format:
+ * [n, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the total number of frequencies.
+ *
* This provides the times a UID's processes spent executing at each different cpu frequency.
* The file contains a monotonically increasing count of time for a single boot. This class
* maintains the previous results of a call to {@link #readDelta} in order to provide a proper
* delta.
+ *
+ * This class uses a throttler to reject any {@link #readDelta} call within
+ * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
+ * which has a shorter throttle interval and returns cached result from last read when the request
+ * is throttled.
+ *
+ * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
+ * the nature of {@link #readDelta(Callback)}).
*/
public class KernelUidCpuFreqTimeReader {
private static final boolean DEBUG = false;
private static final String TAG = "KernelUidCpuFreqTimeReader";
static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
+ // Throttle interval in milliseconds
+ private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
public interface Callback {
void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
}
private long[] mCpuFreqs;
+ private long[] mCurTimes; // Reuse to prevent GC.
+ private long[] mDeltaTimes; // Reuse to prevent GC.
+ private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
private int mCpuFreqsCount;
- private long mLastTimeReadMs;
+ private long mLastTimeReadMs = Long.MIN_VALUE;
private long mNowTimeMs;
+ private boolean mReadBinary = true;
+ private final KernelCpuProcReader mProcReader;
private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
@@ -69,6 +92,15 @@
private boolean mPerClusterTimesAvailable;
private boolean mAllUidTimesAvailable = true;
+ public KernelUidCpuFreqTimeReader() {
+ mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance();
+ }
+
+ @VisibleForTesting
+ public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) {
+ mProcReader = procReader;
+ }
+
public boolean perClusterTimesAvailable() {
return mPerClusterTimesAvailable;
}
@@ -83,7 +115,6 @@
public long[] readFreqs(@NonNull PowerProfile powerProfile) {
checkNotNull(powerProfile);
-
if (mCpuFreqs != null) {
// No need to read cpu freqs more than once.
return mCpuFreqs;
@@ -115,15 +146,37 @@
return readCpuFreqs(line, powerProfile);
}
+ public void setReadBinary(boolean readBinary) {
+ mReadBinary = readBinary;
+ }
+
+ public void setThrottleInterval(long throttleInterval) {
+ if (throttleInterval >= 0) {
+ mThrottleInterval = throttleInterval;
+ }
+ }
+
public void readDelta(@Nullable Callback callback) {
if (mCpuFreqs == null) {
return;
}
+ if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
+ Slog.w(TAG, "Throttle");
+ return;
+ }
+ mNowTimeMs = SystemClock.elapsedRealtime();
+ if (mReadBinary) {
+ readDeltaBinary(callback);
+ } else {
+ readDeltaString(callback);
+ }
+ mLastTimeReadMs = mNowTimeMs;
+ }
+
+ private void readDeltaString(@Nullable Callback callback) {
final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- mNowTimeMs = SystemClock.elapsedRealtime();
readDelta(reader, callback);
- mLastTimeReadMs = mNowTimeMs;
} catch (IOException e) {
Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
} finally {
@@ -131,6 +184,58 @@
}
}
+ @VisibleForTesting
+ public void readDeltaBinary(@Nullable Callback callback) {
+ synchronized (mProcReader) {
+ ByteBuffer bytes = mProcReader.readBytes();
+ if (bytes == null || bytes.remaining() <= 4) {
+ // Error already logged in mProcReader.
+ return;
+ }
+ if ((bytes.remaining() & 3) != 0) {
+ Slog.wtf(TAG, "Cannot parse cluster time proc bytes to int: " + bytes.remaining());
+ return;
+ }
+ IntBuffer buf = bytes.asIntBuffer();
+ final int freqs = buf.get();
+ if (freqs != mCpuFreqsCount) {
+ Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs);
+ return;
+ }
+ if (buf.remaining() % (freqs + 1) != 0) {
+ Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1));
+ return;
+ }
+ int numUids = buf.remaining() / (freqs + 1);
+ for (int i = 0; i < numUids; i++) {
+ int uid = buf.get();
+ long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid);
+ if (lastTimes == null) {
+ lastTimes = new long[mCpuFreqsCount];
+ mLastUidCpuFreqTimeMs.put(uid, lastTimes);
+ }
+ boolean notify = false;
+ boolean corrupted = false;
+ for (int j = 0; j < freqs; j++) {
+ mCurTimes[j] = (long) buf.get() * 10; // Unit is 10ms.
+ mDeltaTimes[j] = mCurTimes[j] - lastTimes[j];
+ if (mCurTimes[j] < 0 || mDeltaTimes[j] < 0) {
+ Slog.e(TAG, "Unexpected data from freq time proc: " + mCurTimes[j]);
+ corrupted = true;
+ }
+ notify |= mDeltaTimes[j] > 0;
+ }
+ if (notify && !corrupted) {
+ System.arraycopy(mCurTimes, 0, lastTimes, 0, freqs);
+ if (callback != null) {
+ callback.onUidCpuFreqTime(uid, mDeltaTimes);
+ }
+ }
+ }
+ // Slog.i(TAG, "Read uids: "+numUids);
+ }
+ }
+
public void removeUid(int uid) {
mLastUidCpuFreqTimeMs.delete(uid);
}
@@ -212,6 +317,8 @@
// First item would be "uid: " which needs to be ignored.
mCpuFreqsCount = freqStr.length - 1;
mCpuFreqs = new long[mCpuFreqsCount];
+ mCurTimes = new long[mCpuFreqsCount];
+ mDeltaTimes = new long[mCpuFreqsCount];
for (int i = 0; i < mCpuFreqsCount; ++i) {
mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 111934f..8b1de2f 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -63,6 +63,7 @@
private static final int ALLOW_APP_CONFIGS = 0x08;
private static final int ALLOW_PRIVAPP_PERMISSIONS = 0x10;
private static final int ALLOW_OEM_PERMISSIONS = 0x20;
+ private static final int ALLOW_HIDDENAPI_WHITELISTING = 0x40;
private static final int ALLOW_ALL = ~0;
// Group-ids that are given to all packages as read from etc/permissions/*.xml.
@@ -137,6 +138,9 @@
// These are the permitted backup transport service components
final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
+ // Package names that are exempted from private API blacklisting
+ final ArraySet<String> mHiddenApiPackageWhitelist = new ArraySet<>();
+
// These are the packages of carrier-associated apps which should be disabled until used until
// a SIM is inserted which grants carrier privileges to that carrier app.
final ArrayMap<String, List<String>> mDisabledUntilUsedPreinstalledCarrierAssociatedApps =
@@ -215,6 +219,10 @@
return mSystemUserBlacklistedApps;
}
+ public ArraySet<String> getHiddenApiWhitelistedApps() {
+ return mHiddenApiPackageWhitelist;
+ }
+
public ArraySet<ComponentName> getDefaultVrComponents() {
return mDefaultVrComponents;
}
@@ -376,6 +384,7 @@
boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS) != 0;
boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
+ boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING) != 0;
while (true) {
XmlUtils.nextElement(parser);
if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
@@ -637,6 +646,15 @@
}
} else if ("oem-permissions".equals(name) && allowOemPermissions) {
readOemPermissions(parser);
+ } else if ("hidden-api-whitelisted-app".equals(name) && allowApiWhitelisting) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<hidden-api-whitelisted-app> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mHiddenApiPackageWhitelist.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
} else {
XmlUtils.skipCurrentTag(parser);
continue;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 33f80ce..b048977 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -64,7 +64,7 @@
"android_graphics_drawable_VectorDrawable.cpp",
"android_view_DisplayEventReceiver.cpp",
"android_view_DisplayListCanvas.cpp",
- "android_view_HardwareLayer.cpp",
+ "android_view_TextureLayer.cpp",
"android_view_InputChannel.cpp",
"android_view_InputDevice.cpp",
"android_view_InputEventReceiver.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d202173..f280c7a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -142,7 +142,7 @@
extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
-extern int register_android_view_HardwareLayer(JNIEnv* env);
+extern int register_android_view_TextureLayer(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
@@ -1370,7 +1370,7 @@
REG_JNI(register_android_view_RenderNode),
REG_JNI(register_android_view_RenderNodeAnimator),
REG_JNI(register_android_view_DisplayListCanvas),
- REG_JNI(register_android_view_HardwareLayer),
+ REG_JNI(register_android_view_TextureLayer),
REG_JNI(register_android_view_ThreadedRenderer),
REG_JNI(register_android_view_Surface),
REG_JNI(register_android_view_SurfaceControl),
diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
index 98f4733..7956bf4 100644
--- a/core/jni/android_view_DisplayListCanvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -159,7 +159,7 @@
canvas->drawRenderNode(renderNode);
}
-static void android_view_DisplayListCanvas_drawLayer(jlong canvasPtr, jlong layerPtr) {
+static void android_view_DisplayListCanvas_drawTextureLayer(jlong canvasPtr, jlong layerPtr) {
Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr);
canvas->drawLayer(layer);
@@ -210,7 +210,7 @@
{ "nInsertReorderBarrier", "(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier },
{ "nFinishRecording", "(J)J", (void*) android_view_DisplayListCanvas_finishRecording },
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode },
- { "nDrawLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawLayer },
+ { "nDrawTextureLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawTextureLayer },
{ "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps },
{ "nDrawRoundRect", "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps },
};
diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_TextureLayer.cpp
similarity index 73%
rename from core/jni/android_view_HardwareLayer.cpp
rename to core/jni/android_view_TextureLayer.cpp
index d934870..e14c46f 100644
--- a/core/jni/android_view_HardwareLayer.cpp
+++ b/core/jni/android_view_TextureLayer.cpp
@@ -40,7 +40,7 @@
using namespace uirenderer;
-static jboolean android_view_HardwareLayer_prepare(JNIEnv* env, jobject clazz,
+static jboolean TextureLayer_prepare(JNIEnv* env, jobject clazz,
jlong layerUpdaterPtr, jint width, jint height, jboolean isOpaque) {
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
bool changed = false;
@@ -49,7 +49,7 @@
return changed;
}
-static void android_view_HardwareLayer_setLayerPaint(JNIEnv* env, jobject clazz,
+static void TextureLayer_setLayerPaint(JNIEnv* env, jobject clazz,
jlong layerUpdaterPtr, jlong paintPtr) {
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
if (layer) {
@@ -58,21 +58,21 @@
}
}
-static void android_view_HardwareLayer_setTransform(JNIEnv* env, jobject clazz,
+static void TextureLayer_setTransform(JNIEnv* env, jobject clazz,
jlong layerUpdaterPtr, jlong matrixPtr) {
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
layer->setTransform(matrix);
}
-static void android_view_HardwareLayer_setSurfaceTexture(JNIEnv* env, jobject clazz,
+static void TextureLayer_setSurfaceTexture(JNIEnv* env, jobject clazz,
jlong layerUpdaterPtr, jobject surface) {
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
sp<GLConsumer> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, surface));
layer->setSurfaceTexture(surfaceTexture);
}
-static void android_view_HardwareLayer_updateSurfaceTexture(JNIEnv* env, jobject clazz,
+static void TextureLayer_updateSurfaceTexture(JNIEnv* env, jobject clazz,
jlong layerUpdaterPtr) {
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
layer->updateTexImage();
@@ -82,18 +82,18 @@
// JNI Glue
// ----------------------------------------------------------------------------
-const char* const kClassPathName = "android/view/HardwareLayer";
+const char* const kClassPathName = "android/view/TextureLayer";
static const JNINativeMethod gMethods[] = {
- { "nPrepare", "(JIIZ)Z", (void*) android_view_HardwareLayer_prepare },
- { "nSetLayerPaint", "(JJ)V", (void*) android_view_HardwareLayer_setLayerPaint },
- { "nSetTransform", "(JJ)V", (void*) android_view_HardwareLayer_setTransform },
+ { "nPrepare", "(JIIZ)Z", (void*) TextureLayer_prepare },
+ { "nSetLayerPaint", "(JJ)V", (void*) TextureLayer_setLayerPaint },
+ { "nSetTransform", "(JJ)V", (void*) TextureLayer_setTransform },
{ "nSetSurfaceTexture", "(JLandroid/graphics/SurfaceTexture;)V",
- (void*) android_view_HardwareLayer_setSurfaceTexture },
- { "nUpdateSurfaceTexture", "(J)V", (void*) android_view_HardwareLayer_updateSurfaceTexture },
+ (void*) TextureLayer_setSurfaceTexture },
+ { "nUpdateSurfaceTexture", "(J)V", (void*) TextureLayer_updateSurfaceTexture },
};
-int register_android_view_HardwareLayer(JNIEnv* env) {
+int register_android_view_TextureLayer(JNIEnv* env) {
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 9f9fd05..9915174 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -226,7 +226,11 @@
LTE = 13;
EHRPD = 14;
HSPAP = 15;
- OTHER = 16;
+ GSM = 16;
+ TD_SCDMA = 17;
+ IWLAN = 18;
+ LTE_CA = 19;
+ OTHER = 20;
};
optional Name name = 1;
optional TimerProto total = 2;
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index f5d77d6..0b8a5da 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -58,7 +58,7 @@
}
repeated MigrateType migrate_types = 3;
- // Next tag: 9
+ // Next tag: 10
message Block {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -77,6 +77,8 @@
optional int32 reserve = 7;
optional int32 isolate = 8;
+
+ optional int32 highatomic = 9;
}
repeated Block blocks = 4;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index caeca592..2f78345 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3810,9 +3810,15 @@
<permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
android:protectionLevel="signature|development|instant|appop" />
- <!-- @hide Allows system components to access all app shortcuts. -->
+ <!-- @SystemApi Allows to access all app shortcuts.
+ @hide -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|textClassifier" />
+
+ <!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
+ @hide -->
+ <permission android:name="android.permission.UNLIMITED_SHORTCUTS_API_CALLS"
+ android:protectionLevel="signature|textClassifier" />
<!-- @SystemApi Allows an application to read the runtime profiles of other apps.
@hide <p>Not for use by third-party applications. -->
diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml
index 81d1300..c298b80 100644
--- a/core/res/res/anim/task_close_enter.xml
+++ b/core/res/res/anim/task_close_enter.xml
@@ -17,7 +17,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
- android:zAdjustment="top">
+ android:zAdjustment="top"
+ android:showWallpaper="true">
<alpha
android:fromAlpha="1"
diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml
index ab8b89c..9394c57 100644
--- a/core/res/res/anim/task_close_exit.xml
+++ b/core/res/res/anim/task_close_exit.xml
@@ -17,7 +17,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false"
+ android:showWallpaper="true">
<alpha
android:fromAlpha="1.0"
diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml
index 0e66eda..e23201f 100644
--- a/core/res/res/anim/task_open_enter.xml
+++ b/core/res/res/anim/task_open_enter.xml
@@ -19,7 +19,8 @@
<!-- This should in sync with cross_profile_apps_thumbnail_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
- android:zAdjustment="top">
+ android:zAdjustment="top"
+ android:showWallpaper="true">
<alpha
android:fromAlpha="1"
diff --git a/core/res/res/anim/task_open_enter_cross_profile_apps.xml b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
index a92425e..defea08 100644
--- a/core/res/res/anim/task_open_enter_cross_profile_apps.xml
+++ b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
@@ -19,7 +19,8 @@
<!-- This should in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
- android:zAdjustment="top">
+ android:zAdjustment="top"
+ android:showWallpaper="true">
<alpha
android:fromAlpha="1"
diff --git a/core/res/res/anim/task_open_exit.xml b/core/res/res/anim/task_open_exit.xml
index ecb98ce..c9ade22 100644
--- a/core/res/res/anim/task_open_exit.xml
+++ b/core/res/res/anim/task_open_exit.xml
@@ -17,7 +17,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false"
+ android:showWallpaper="true">
<alpha
android:fromAlpha="1.0"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 49b0ee7..07559f4 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -24,11 +24,9 @@
android:layout_width="match_parent"
android:layout_height="@dimen/notification_action_list_height"
android:paddingEnd="12dp"
- android:paddingStart="8dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:visibility="gone"
- android:background="@color/notification_action_list"
>
<!-- actions will be added here -->
</com.android.internal.widget.NotificationActionListLayout>
diff --git a/core/res/res/layout/notification_material_reply_text.xml b/core/res/res/layout/notification_material_reply_text.xml
index bc22eb4..84603b0 100644
--- a/core/res/res/layout/notification_material_reply_text.xml
+++ b/core/res/res/layout/notification_material_reply_text.xml
@@ -28,7 +28,8 @@
android:layout_width="match_parent"
android:layout_height="1dip"
android:id="@+id/action_divider"
- android:layout_marginBottom="15dp"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginBottom="@dimen/notification_content_margin"
android:background="@drawable/notification_template_divider" />
<TextView
@@ -53,7 +54,6 @@
android:id="@+id/notification_material_reply_text_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="15dp"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:textAppearance="@style/TextAppearance.Material.Notification.Reply"
android:singleLine="true" />
diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index 19c4d23..525d493 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -88,7 +88,6 @@
android:orientation="horizontal"
android:gravity="center"
android:visibility="gone"
- android:background="@color/notification_action_list"
/>
</FrameLayout>
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 445b19b..221bcf6 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -30,7 +30,7 @@
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:layout_marginTop="@dimen/notification_content_margin_top"
- android:layout_marginBottom="@dimen/notification_content_margin_bottom"
+ android:layout_marginBottom="@dimen/notification_content_margin"
android:orientation="vertical" >
<include layout="@layout/notification_template_part_line1" />
<include layout="@layout/notification_template_text" />
@@ -42,7 +42,7 @@
<include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin_bottom" />
+ android:layout_marginTop="@dimen/notification_content_margin" />
</LinearLayout>
<include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index d47bff6..2106890 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -20,6 +20,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
+ android:clipChildren="false"
android:tag="big" >
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
@@ -30,6 +31,7 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_weight="1"
android:layout_gravity="top" >
<include layout="@layout/notification_template_header" />
<LinearLayout
@@ -39,7 +41,6 @@
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:layout_marginTop="@dimen/notification_content_margin_top"
- android:layout_marginBottom="@dimen/notification_content_margin_bottom"
android:orientation="vertical" >
<include layout="@layout/notification_template_part_line1" />
<include layout="@layout/notification_template_text" />
@@ -61,7 +62,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
+ android:layout_marginTop="@dimen/notification_content_margin" />
+ <include layout="@layout/notification_material_action_list" />
</LinearLayout>
- <include layout="@layout/notification_material_action_list" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index cc4dd387..b4e26483 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -46,7 +46,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/notification_content_margin_top"
android:layout_marginStart="@dimen/notification_content_margin_start"
- android:layout_marginBottom="@dimen/notification_content_margin_bottom"
+ android:layout_marginBottom="@dimen/notification_content_margin"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:minHeight="@dimen/notification_min_content_height"
android:orientation="vertical"
diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml
index 76c0a67..7a1cc1e 100644
--- a/core/res/res/layout/notification_template_material_big_picture.xml
+++ b/core/res/res/layout/notification_template_material_big_picture.xml
@@ -20,17 +20,18 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="bigPicture"
+ android:clipChildren="false"
>
<include layout="@layout/notification_template_header" />
<include layout="@layout/notification_template_right_icon" />
<LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
android:layout_marginTop="@dimen/notification_content_margin_top"
android:clipToPadding="false"
android:orientation="vertical"
- android:id="@+id/notification_action_list_margin_target"
>
<LinearLayout
android:id="@+id/notification_main_column"
@@ -53,7 +54,6 @@
android:adjustViewBounds="true"
android:layout_weight="1"
android:layout_marginTop="13dp"
- android:layout_marginBottom="16dp"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:scaleType="centerCrop"
@@ -69,7 +69,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
+ android:layout_marginTop="@dimen/notification_content_margin" />
+ <include layout="@layout/notification_material_action_list" />
</LinearLayout>
- <include layout="@layout/notification_material_action_list" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index ac4c052..3a6f573 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -19,6 +19,7 @@
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:clipChildren="false"
android:tag="bigText"
>
<include layout="@layout/notification_template_header" />
@@ -43,6 +44,7 @@
android:clipToPadding="false"
android:minHeight="@dimen/notification_min_content_height"
android:orientation="vertical"
+ android:layout_weight="1"
>
<include layout="@layout/notification_template_part_line1" />
<include layout="@layout/notification_template_progress"
@@ -54,7 +56,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/notification_text_margin_top"
- android:paddingBottom="@dimen/notification_content_margin_bottom"
android:textAppearance="@style/TextAppearance.Material.Notification"
android:singleLine="false"
android:gravity="top"
@@ -68,12 +69,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/notification_template_smart_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin" />
+ <include layout="@layout/notification_material_action_list" />
</LinearLayout>
- <include layout="@layout/notification_material_action_list" />
<include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index 718cf16..23d8799 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -20,6 +20,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="inbox"
+ android:clipChildren="false"
>
<include layout="@layout/notification_template_header" />
<LinearLayout
@@ -38,8 +39,8 @@
android:layout_gravity="top"
android:paddingStart="@dimen/notification_content_margin_start"
android:paddingEnd="@dimen/notification_content_margin_end"
- android:paddingBottom="@dimen/notification_content_margin_bottom"
android:minHeight="@dimen/notification_min_content_height"
+ android:layout_weight="1"
android:clipToPadding="false"
android:orientation="vertical"
>
@@ -124,8 +125,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginBottom="@dimen/notification_content_margin_bottom" />
+ android:layout_marginTop="@dimen/notification_content_margin" />
+ <include layout="@layout/notification_material_action_list" />
</LinearLayout>
- <include layout="@layout/notification_material_action_list" />
<include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 2c69b90..3a0912b 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -49,7 +49,7 @@
android:layout_gravity="fill_vertical"
android:layout_weight="1"
android:minHeight="@dimen/notification_min_content_height"
- android:paddingBottom="@dimen/notification_content_margin_bottom"
+ android:paddingBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1"/>
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index 34f5ae8..53514a3 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -19,10 +19,11 @@
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:clipChildren="false"
android:tag="messaging"
>
<include layout="@layout/notification_template_header"/>
- <LinearLayout
+ <com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -31,15 +32,15 @@
android:clipToPadding="false"
android:orientation="vertical">
- <LinearLayout
+ <com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
+ android:layout_weight="1"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:minHeight="@dimen/notification_min_content_height"
- android:layout_marginBottom="@dimen/notification_content_margin_bottom"
android:orientation="vertical"
>
<com.android.internal.widget.MessagingLinearLayout
@@ -47,12 +48,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spacing="@dimen/notification_messaging_spacing" />
- <include layout="@layout/notification_template_smart_reply_container"
+ </com.android.internal.widget.RemeasuringLinearLayout>
+ <include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/notification_content_margin_bottom" />
- </LinearLayout>
- </LinearLayout>
- <include layout="@layout/notification_material_action_list" />
+ android:layout_marginTop="@dimen/notification_content_margin" />
+ <include layout="@layout/notification_material_action_list" />
+ </com.android.internal.widget.RemeasuringLinearLayout>
<include layout="@layout/notification_template_right_icon"/>
</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d26567e..9d7432e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6328,6 +6328,9 @@
<!-- Special option for window animations: if this window is on top
of a wallpaper, don't animate the wallpaper with it. -->
<attr name="detachWallpaper" format="boolean" />
+ <!-- Special option for window animations: show the wallpaper behind when running this
+ animation. -->
+ <attr name="showWallpaper" format="boolean" />
</declare-styleable>
<declare-styleable name="AnimationSet">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 32fe2b2..cfb5784 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -173,10 +173,10 @@
values listed below. If no protectionLevel is defined for a custom
permission, the system assigns the default ("normal").
<p>Each protection level consists of a base permission type and zero or
- more flags:
+ more flags. Use the following functions to extract those.
<pre>
- int basePermissionType = protectionLevel & {@link android.content.pm.PermissionInfo#PROTECTION_MASK_BASE};
- int permissionFlags = protectionLevel & {@link android.content.pm.PermissionInfo#PROTECTION_MASK_FLAGS};
+ int basePermissionType = permissionInfo.getProtection();
+ int permissionFlags = permissionInfo.getProtectionFlags();
</pre>
-->
<attr name="protectionLevel">
@@ -265,6 +265,9 @@
<!-- Additional flag from base permission type: this permission can be granted to
privileged apps in vendor partition. -->
<flag name="vendorPrivileged" value="0x8000" />
+ <!-- Additional flag from base permission type: this permission can be automatically
+ granted to the system default text classifier -->
+ <flag name="textClassifier" value="0x10000" />
</attr>
<!-- Flags indicating more context for a permission group. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 01da14f..3697f3e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2372,7 +2372,7 @@
obtain user consent to access their location through other means. -->
<string-array name="config_disabledUntilUsedPreinstalledCarrierApps" translatable="false" />
- <!-- The list of classes that should be added to the notification ranking pipline.
+ <!-- The list of classes that should be added to the notification ranking pipeline.
See {@link com.android.server.notification.NotificationSignalExtractor}
If you add a new extractor to this list make sure to update
NotificationManagerService.handleRankingSort()-->
@@ -2384,11 +2384,14 @@
<!-- depends on AdjustmentExtractor-->
<item>com.android.server.notification.ValidateNotificationPeople</item>
<item>com.android.server.notification.PriorityExtractor</item>
+ <!-- depends on PriorityExtractor -->
+ <item>com.android.server.notification.ZenModeExtractor</item>
<item>com.android.server.notification.ImportanceExtractor</item>
<!-- depends on ImportanceExtractor-->
<item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
<item>com.android.server.notification.VisibilityExtractor</item>
<item>com.android.server.notification.BadgeExtractor</item>
+
</string-array>
<!-- Flag indicating that this device does not rotate and will always remain in its default
@@ -3158,14 +3161,14 @@
-->
<string name="config_defaultAutofillService" translatable="false"></string>
- <!-- The component name, flattened to a string, for the default system textclassifier service.
+ <!-- The package name for the default system textclassifier service.
This service must be trusted, as it can be activated without explicit consent of the user.
- (e.g. com.android.textclassifier/.TextClassifierServiceImpl).
+ Example: "com.android.textclassifier"
If no textclassifier service with the specified name exists on the device (or if this is
set to empty string), a default textclassifier will be loaded in the calling app's process.
See android.view.textclassifier.TextClassificationManager.
-->
- <string name="config_defaultTextClassifierService" translatable="false"></string>
+ <string name="config_defaultTextClassifierPackage" translatable="false"></string>
<!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
<bool name="config_useDefaultFocusHighlight">true</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index cfaab6a..2ce08eb 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -176,13 +176,13 @@
<dimen name="notification_extra_margin_ambient">16dp</dimen>
<!-- The height of the notification action list -->
- <dimen name="notification_action_list_height">56dp</dimen>
+ <dimen name="notification_action_list_height">60dp</dimen>
<!-- height of the content margin to accomodate for the header -->
<dimen name="notification_content_margin_top">46dp</dimen>
- <!-- height of the content margin on the bottom -->
- <dimen name="notification_content_margin_bottom">20dp</dimen>
+ <!-- height of the content margin that is applied at the end of the notification content -->
+ <dimen name="notification_content_margin">20dp</dimen>
<!-- The height of the progress bar. -->
<dimen name="notification_progress_bar_height">15dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 863a8cd..0493c2b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2646,7 +2646,7 @@
<java-symbol type="dimen" name="notification_content_margin_end" />
<java-symbol type="dimen" name="notification_content_picture_margin" />
<java-symbol type="dimen" name="notification_content_margin_top" />
- <java-symbol type="dimen" name="notification_content_margin_bottom" />
+ <java-symbol type="dimen" name="notification_content_margin" />
<java-symbol type="dimen" name="notification_header_background_height" />
<java-symbol type="dimen" name="notification_header_height" />
<java-symbol type="dimen" name="notification_header_expand_icon_size" />
@@ -3111,7 +3111,7 @@
<java-symbol type="string" name="notification_channel_heavy_weight_app" />
<java-symbol type="string" name="notification_channel_system_changes" />
<java-symbol type="string" name="config_defaultAutofillService" />
- <java-symbol type="string" name="config_defaultTextClassifierService" />
+ <java-symbol type="string" name="config_defaultTextClassifierPackage" />
<java-symbol type="string" name="notification_channel_foreground_service" />
<java-symbol type="string" name="foreground_service_app_in_background" />
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index b7869d0..5407ce6 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -41,7 +41,7 @@
@RunWith(AndroidJUnit4.class)
public class TextClassificationManagerTest {
- private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
+ private static final LocaleList LOCALES = LocaleList.forLanguageTags("en-US");
private static final String NO_TYPE = null;
private TextClassificationManager mTcm;
@@ -181,6 +181,42 @@
}
@Test
+ public void testTextClassifyText_date() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Let's meet on January 9, 2018.";
+ String classifiedText = "January 9, 2018";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+
+ TextClassification classification = mClassifier.classifyText(
+ text, startIndex, endIndex, mClassificationOptions);
+ assertThat(classification,
+ isTextClassification(
+ classifiedText,
+ TextClassifier.TYPE_DATE,
+ null));
+ }
+
+ @Test
+ public void testTextClassifyText_datetime() {
+ if (isTextClassifierDisabled()) return;
+
+ String text = "Let's meet 2018/01/01 10:30:20.";
+ String classifiedText = "2018/01/01 10:30:20";
+ int startIndex = text.indexOf(classifiedText);
+ int endIndex = startIndex + classifiedText.length();
+
+ TextClassification classification = mClassifier.classifyText(
+ text, startIndex, endIndex, mClassificationOptions);
+ assertThat(classification,
+ isTextClassification(
+ classifiedText,
+ TextClassifier.TYPE_DATE_TIME,
+ null));
+ }
+
+ @Test
public void testGenerateLinks_phone() {
if (isTextClassifierDisabled()) return;
String text = "The number is +12122537077. See you tonight!";
@@ -334,7 +370,8 @@
&& text.equals(result.getText())
&& result.getEntityCount() > 0
&& type.equals(result.getEntity(0))
- && intentUri.equals(result.getIntent().getDataString());
+ && (intentUri == null
+ || intentUri.equals(result.getIntent().getDataString()));
// TODO: Include other properties.
}
return false;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierConstantsTest.java
new file mode 100644
index 0000000..984eede
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierConstantsTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassifierConstantsTest {
+
+ @Test
+ public void testEntityListParsing() {
+ final TextClassifierConstants constants = TextClassifierConstants.loadFromString(
+ "entity_list_default=phone,"
+ + "entity_list_not_editable=address:flight,"
+ + "entity_list_editable=date:datetime");
+ assertEquals(1, constants.getEntityListDefault().size());
+ assertEquals("phone", constants.getEntityListDefault().get(0));
+ assertEquals(2, constants.getEntityListNotEditable().size());
+ assertEquals("address", constants.getEntityListNotEditable().get(0));
+ assertEquals("flight", constants.getEntityListNotEditable().get(1));
+ assertEquals(2, constants.getEntityListEditable().size());
+ assertEquals("date", constants.getEntityListEditable().get(0));
+ assertEquals("datetime", constants.getEntityListEditable().get(1));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/GenerateLinksLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/GenerateLinksLoggerTest.java
new file mode 100644
index 0000000..b920ca3
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/logging/GenerateLinksLoggerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.logging;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+
+import android.metrics.LogMaker;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GenerateLinksLoggerTest {
+
+ private static final String PACKAGE_NAME = "packageName";
+ private static final String ZERO = "0";
+ private static final int LATENCY_MS = 123;
+
+ @Test
+ public void testLogGenerateLinks() {
+ final String phoneText = "+12122537077";
+ final String addressText = "1600 Amphitheater Parkway, Mountain View, CA";
+ final String testText = "The number is " + phoneText + ", the address is " + addressText;
+ final int phoneOffset = testText.indexOf(phoneText);
+ final int addressOffset = testText.indexOf(addressText);
+
+ final Map<String, Float> phoneEntityScores = new ArrayMap<>();
+ phoneEntityScores.put(TextClassifier.TYPE_PHONE, 0.9f);
+ phoneEntityScores.put(TextClassifier.TYPE_OTHER, 0.1f);
+ final Map<String, Float> addressEntityScores = new ArrayMap<>();
+ addressEntityScores.put(TextClassifier.TYPE_ADDRESS, 1f);
+
+ TextLinks links = new TextLinks.Builder(testText)
+ .addLink(phoneOffset, phoneOffset + phoneText.length(), phoneEntityScores)
+ .addLink(addressOffset, addressOffset + addressText.length(), addressEntityScores)
+ .build();
+
+ // Set up mock.
+ MetricsLogger metricsLogger = mock(MetricsLogger.class);
+ ArgumentCaptor<LogMaker> logMakerCapture = ArgumentCaptor.forClass(LogMaker.class);
+ doNothing().when(metricsLogger).write(logMakerCapture.capture());
+
+ // Generate the log.
+ GenerateLinksLogger logger = new GenerateLinksLogger(1 /* sampleRate */, metricsLogger);
+ logger.logGenerateLinks(testText, links, PACKAGE_NAME, LATENCY_MS);
+
+ // Validate.
+ List<LogMaker> logs = logMakerCapture.getAllValues();
+ assertEquals(3, logs.size());
+ assertHasLog(logs, "" /* entityType */, 2, phoneText.length() + addressText.length(),
+ testText.length());
+ assertHasLog(logs, TextClassifier.TYPE_ADDRESS, 1, addressText.length(),
+ testText.length());
+ assertHasLog(logs, TextClassifier.TYPE_PHONE, 1, phoneText.length(),
+ testText.length());
+ }
+
+ private void assertHasLog(List<LogMaker> logs, String entityType, int numLinks,
+ int linkTextLength, int textLength) {
+ for (LogMaker log : logs) {
+ if (!entityType.equals(getEntityType(log))) {
+ continue;
+ }
+ assertEquals(PACKAGE_NAME, log.getPackageName());
+ assertNotNull(Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID)));
+ assertEquals(numLinks, getIntValue(log, MetricsEvent.FIELD_LINKIFY_NUM_LINKS));
+ assertEquals(linkTextLength, getIntValue(log, MetricsEvent.FIELD_LINKIFY_LINK_LENGTH));
+ assertEquals(textLength, getIntValue(log, MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH));
+ assertEquals(LATENCY_MS, getIntValue(log, MetricsEvent.FIELD_LINKIFY_LATENCY));
+ return;
+ }
+ fail("No log for entity type \"" + entityType + "\"");
+ }
+
+ private static String getEntityType(LogMaker log) {
+ return Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE), "");
+ }
+
+ private static int getIntValue(LogMaker log, int eventField) {
+ return Integer.parseInt(Objects.toString(log.getTaggedData(eventField), ZERO));
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 702f4b8..98b7a3f 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -36,6 +36,7 @@
BatteryStatsTimerTest.class,
BatteryStatsUidTest.class,
BatteryStatsUserLifecycleTests.class,
+ KernelCpuProcReaderTest.class,
KernelMemoryBandwidthStatsTest.class,
KernelSingleUidTimeReaderTest.class,
KernelUidCpuFreqTimeReaderTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java
new file mode 100644
index 0000000..efdd7e9
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Test class for {@link KernelCpuProcReader}.
+ *
+ * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReader
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuProcReaderTest {
+
+ private File mRoot;
+ private File mTestDir;
+ private File mTestFile;
+ private Random mRand = new Random();
+
+ private KernelCpuProcReader mKernelCpuProcReader;
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
+ public void setUp() {
+ mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
+ mRoot = getContext().getFilesDir();
+ mTestFile = new File(mTestDir, "test.file");
+ mKernelCpuProcReader = new KernelCpuProcReader(mTestFile.getAbsolutePath());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mTestDir);
+ FileUtils.deleteContents(mRoot);
+ }
+
+
+ /**
+ * Tests that reading will return null if the file does not exist.
+ */
+ @Test
+ public void testReadInvalidFile() throws Exception {
+ assertEquals(null, mKernelCpuProcReader.readBytes());
+ }
+
+ /**
+ * Tests that reading will always return null after 5 failures.
+ */
+ @Test
+ public void testReadErrorsLimit() throws Exception {
+ mKernelCpuProcReader.setThrottleInterval(0);
+ for (int i = 0; i < 3; i++) {
+ assertNull(mKernelCpuProcReader.readBytes());
+ SystemClock.sleep(50);
+ }
+
+ final byte[] data = new byte[1024];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+
+ assertTrue(mTestFile.delete());
+ for (int i = 0; i < 3; i++) {
+ assertNull(mKernelCpuProcReader.readBytes());
+ SystemClock.sleep(50);
+ }
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertNull(mKernelCpuProcReader.readBytes());
+ }
+
+ /**
+ * Tests reading functionality.
+ */
+ @Test
+ public void testSimpleRead() throws Exception {
+ final byte[] data = new byte[1024];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ }
+
+ /**
+ * Tests multiple reading functionality.
+ */
+ @Test
+ public void testMultipleRead() throws Exception {
+ mKernelCpuProcReader.setThrottleInterval(0);
+ for (int i = 0; i < 100; i++) {
+ final byte[] data = new byte[mRand.nextInt(102400) + 4];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ assertTrue(mTestFile.delete());
+ }
+ }
+
+ /**
+ * Tests reading with resizing.
+ */
+ @Test
+ public void testReadWithResize() throws Exception {
+ final byte[] data = new byte[128001];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ }
+
+ /**
+ * Tests that reading a file over the limit (1MB) will return null.
+ */
+ @Test
+ public void testReadOverLimit() throws Exception {
+ final byte[] data = new byte[1228800];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertNull(mKernelCpuProcReader.readBytes());
+ }
+
+ /**
+ * Tests throttling. Deleting underlying file should not affect cache.
+ */
+ @Test
+ public void testThrottle() throws Exception {
+ mKernelCpuProcReader.setThrottleInterval(3000);
+ final byte[] data = new byte[20001];
+ mRand.nextBytes(data);
+ try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) {
+ os.write(data);
+ }
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ assertTrue(mTestFile.delete());
+ for (int i = 0; i < 5; i++) {
+ assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes())));
+ SystemClock.sleep(10);
+ }
+ SystemClock.sleep(5000);
+ assertNull(mKernelCpuProcReader.readBytes());
+ }
+
+ private byte[] toArray(ByteBuffer buffer) {
+ assertNotNull(buffer);
+ byte[] arr = new byte[buffer.remaining()];
+ buffer.get(arr);
+ return arr;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
index 1ac82bd..312af16 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java
@@ -16,8 +16,6 @@
package com.android.internal.os;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -25,18 +23,15 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.io.BufferedReader;
-import java.util.Arrays;
-import java.util.List;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.Random;
/**
@@ -44,75 +39,64 @@
*
* To run it:
* bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuActiveTimeReaderTest
- *
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelUidCpuActiveTimeReaderTest {
- @Mock private BufferedReader mBufferedReader;
- @Mock private KernelUidCpuActiveTimeReader.Callback mCallback;
-
+ @Mock
+ private KernelCpuProcReader mProcReader;
+ @Mock
+ private KernelUidCpuActiveTimeReader.Callback mCallback;
private KernelUidCpuActiveTimeReader mReader;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mReader = new KernelUidCpuActiveTimeReader();
- }
-
- public class Temp {
-
- public void method() {
- method1(new long[][]{{1,2,3}, {2,3,4}});
- method1(new long[][]{{2,2,3}, {2,3,4}});
- }
- public int method1(long[][] array) {
- return array.length * array[0].length;
- }
+ mReader = new KernelUidCpuActiveTimeReader(mProcReader);
+ mReader.setThrottleInterval(0);
}
@Test
public void testReadDelta() throws Exception {
final int cores = 8;
- final String info = "active: 8";
final int[] uids = {1, 22, 333, 4444, 5555};
final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for(int i=0;i<uids.length;i++){
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
}
verifyNoMoreInteractions(mCallback);
// Verify that a second call will only return deltas.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times1 = increaseTime(times);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for(int i=0;i<uids.length;i++){
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times1[i], times[i])));
}
verifyNoMoreInteractions(mCallback);
// Verify that there won't be a callback if the proc file values didn't change.
- Mockito.reset(mCallback, mBufferedReader);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ Mockito.reset(mCallback);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1));
+ mReader.readDelta(mCallback);
verifyNoMoreInteractions(mCallback);
// Verify that calling with a null callback doesn't result in any crashes
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times2 = increaseTime(times1);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times2));
- mReader.readDeltaInternal(mBufferedReader, null);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
+ mReader.readDelta(null);
// Verify that the readDelta call will only return deltas when
// the previous call had null callback.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times3 = increaseTime(times2);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
+ mReader.readDelta(mCallback);
for (int i = 0; i < uids.length; ++i) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
}
@@ -122,78 +106,76 @@
@Test
public void testReadDelta_malformedData() throws Exception {
final int cores = 8;
- final String info = "active: 8";
final int[] uids = {1, 22, 333, 4444, 5555};
final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for(int i=0;i<uids.length;i++){
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i]));
}
verifyNoMoreInteractions(mCallback);
- // Verify that there is no callback if subsequent call provides wrong # of entries.
- Mockito.reset(mCallback, mBufferedReader);
- final long[][] temp = increaseTime(times);
- final long[][] times1 = new long[uids.length][];
- for(int i=0;i<temp.length;i++){
- times1[i] = Arrays.copyOfRange(temp[i], 0, 6);
- }
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ // Verify that there is no callback if subsequent call is in wrong format.
+ Mockito.reset(mCallback);
+ final long[][] times1 = increaseTime(times);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1).putInt(0, 5));
+ mReader.readDelta(mCallback);
verifyNoMoreInteractions(mCallback);
// Verify that the internal state was not modified if the given core count does not match
// the following # of entries.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times2 = increaseTime(times);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times2));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for(int i=0;i<uids.length;i++){
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times2[i], times[i])));
}
verifyNoMoreInteractions(mCallback);
// Verify that there is no callback if any value in the proc file is -ve.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times3 = increaseTime(times2);
times3[uids.length - 1][cores - 1] *= -1;
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
+ mReader.readDelta(mCallback);
for (int i = 0; i < uids.length - 1; ++i) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i])));
}
verifyNoMoreInteractions(mCallback);
// Verify that the internal state was not modified when the proc file had -ve value.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
for (int i = 0; i < cores; i++) {
- times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 1000;
+ times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1], getTotal(subtract(times3[uids.length - 1], times2[uids.length - 1])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3));
+ mReader.readDelta(mCallback);
+ verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
+ getTotal(subtract(times3[uids.length - 1], times2[uids.length - 1])));
verifyNoMoreInteractions(mCallback);
// Verify that there is no callback if the values in the proc file are decreased.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
final long[][] times4 = increaseTime(times3);
- times4[uids.length - 1][cores - 1] = times3[uids.length - 1][cores - 1] - 1;
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times4));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
+ System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
+ times4[uids.length - 1][cores - 1] -= 100;
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
+ mReader.readDelta(mCallback);
for (int i = 0; i < uids.length - 1; ++i) {
verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times4[i], times3[i])));
}
verifyNoMoreInteractions(mCallback);
// Verify that the internal state was not modified when the proc file had decreased values.
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mCallback);
for (int i = 0; i < cores; i++) {
- times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 1000;
+ times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times4));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1], getTotal(subtract(times4[uids.length - 1], times3[uids.length - 1])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4));
+ mReader.readDelta(mCallback);
+ verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1],
+ getTotal(subtract(times4[uids.length - 1], times3[uids.length - 1])));
verifyNoMoreInteractions(mCallback);
}
@@ -205,36 +187,50 @@
return val;
}
- private String[] formatTime(int[] uids, long[][] times) {
- String[] lines = new String[uids.length + 1];
- for (int i=0;i<uids.length;i++){
- StringBuilder sb = new StringBuilder();
- sb.append(uids[i]).append(':');
- for(int j=0;j<times[i].length;j++){
- sb.append(' ').append(times[i][j]);
- }
- lines[i] = sb.toString();
- }
- lines[uids.length] = null;
- return lines;
- }
-
+ /**
+ * Unit of original and return value is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3,
+ * ..., 10. So that when wedivide shared cpu time by concurrent thread count, we always get a
+ * nice integer, avoiding rounding errors.
+ */
private long[][] increaseTime(long[][] original) {
long[][] newTime = new long[original.length][original[0].length];
Random rand = new Random();
- for(int i = 0;i<original.length;i++){
- for(int j=0;j<original[0].length;j++){
- newTime[i][j] = original[i][j] + rand.nextInt(1000_000) + 10000;
+ for (int i = 0; i < original.length; i++) {
+ for (int j = 0; j < original[0].length; j++) {
+ newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
}
}
return newTime;
}
+ // Unit of times is 10ms
private long getTotal(long[] times) {
long sum = 0;
- for(int i=0;i<times.length;i++){
- sum+=times[i] * 10 / (i+1);
+ for (int i = 0; i < times.length; i++) {
+ sum += times[i] * 10 / (i + 1);
}
return sum;
}
+
+ /**
+ * Format uids and times (in 10ms) into the following format:
+ * [n, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the total number of cpus (num_possible_cpus)
+ */
+ private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
+ int size = (1 + uids.length * (times[0].length + 1)) * 4;
+ ByteBuffer buf = ByteBuffer.allocate(size);
+ buf.order(ByteOrder.nativeOrder());
+ buf.putInt(times[0].length);
+ for (int i = 0; i < uids.length; i++) {
+ buf.putInt(uids[i]);
+ for (int j = 0; j < times[i].length; j++) {
+ buf.putInt((int) times[i][j]);
+ }
+ }
+ buf.flip();
+ return buf.order(ByteOrder.nativeOrder());
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
index 0d1f852..d21f541 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java
@@ -16,26 +16,25 @@
package com.android.internal.os;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
-import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.io.BufferedReader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.Arrays;
-import java.util.List;
import java.util.Random;
/**
@@ -43,147 +42,163 @@
*
* To run it:
* bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuClusterTimeReaderTest
- *
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelUidCpuClusterTimeReaderTest {
- @Mock private BufferedReader mBufferedReader;
- @Mock private KernelUidCpuClusterTimeReader.Callback mCallback;
-
+ @Mock
+ private KernelCpuProcReader mProcReader;
private KernelUidCpuClusterTimeReader mReader;
+ private VerifiableCallback mCallback;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mReader = new KernelUidCpuClusterTimeReader();
+ mReader = new KernelUidCpuClusterTimeReader(mProcReader);
+ mCallback = new VerifiableCallback();
+ mReader.setThrottleInterval(0);
}
@Test
public void testReadDelta() throws Exception {
- final String info = "policy0: 2 policy4: 4";
+ VerifiableCallback cb = new VerifiableCallback();
final int cores = 6;
- final int[] cluster = {2, 4};
+ final int[] clusters = {2, 4};
final int[] uids = {1, 22, 333, 4444, 5555};
// Verify initial call
final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, times[i]));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
+ mReader.readDelta(cb);
+ for (int i = 0; i < uids.length; i++) {
+ cb.verify(uids[i], getTotal(clusters, times[i]));
}
+ cb.verifyNoMoreInteractions();
// Verify that a second call will only return deltas.
- Mockito.reset(mCallback, mBufferedReader);
+ cb.clear();
+ Mockito.reset(mProcReader);
final long[][] times1 = increaseTime(times);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times1[i], times[i])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
+ mReader.readDelta(cb);
+ for (int i = 0; i < uids.length; i++) {
+ cb.verify(uids[i], getTotal(clusters, subtract(times1[i], times[i])));
}
+ cb.verifyNoMoreInteractions();
// Verify that there won't be a callback if the proc file values didn't change.
- Mockito.reset(mCallback, mBufferedReader);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verifyNoMoreInteractions(mCallback);
+ cb.clear();
+ Mockito.reset(mProcReader);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
+ mReader.readDelta(cb);
+ cb.verifyNoMoreInteractions();
// Verify that calling with a null callback doesn't result in any crashes
- Mockito.reset(mCallback, mBufferedReader);
+ Mockito.reset(mProcReader);
final long[][] times2 = increaseTime(times1);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times2));
- mReader.readDeltaInternal(mBufferedReader, null);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
+ mReader.readDelta(null);
// Verify that the readDelta call will only return deltas when
// the previous call had null callback.
- Mockito.reset(mCallback, mBufferedReader);
+ cb.clear();
+ Mockito.reset(mProcReader);
final long[][] times3 = increaseTime(times2);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times3[i], times2[i])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
+ mReader.readDelta(cb);
+ for (int i = 0; i < uids.length; i++) {
+ cb.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
}
+ cb.verifyNoMoreInteractions();
}
@Test
public void testReadDelta_malformedData() throws Exception {
- final String info = "policy0: 2 policy4: 4";
final int cores = 6;
- final int[] cluster = {2, 4};
+ final int[] clusters = {2, 4};
final int[] uids = {1, 22, 333, 4444, 5555};
// Verify initial call
final long[][] times = increaseTime(new long[uids.length][cores]);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, times[i]));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
+ mCallback.verify(uids[i], getTotal(clusters, times[i]));
}
+ mCallback.verifyNoMoreInteractions();
- // Verify that there is no callback if subsequent call provides wrong # of entries.
- Mockito.reset(mCallback, mBufferedReader);
+ // Verify that there is no callback if a call has wrong format
+ mCallback.clear();
+ Mockito.reset(mProcReader);
final long[][] temp = increaseTime(times);
final long[][] times1 = new long[uids.length][];
- for(int i=0;i<temp.length;i++){
+ for (int i = 0; i < temp.length; i++) {
times1[i] = Arrays.copyOfRange(temp[i], 0, 4);
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times1));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verifyNoMoreInteractions(mCallback);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
+ mReader.readDelta(mCallback);
+ mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified if the given core count does not match
// the following # of entries.
- Mockito.reset(mCallback, mBufferedReader);
+ mCallback.clear();
+ Mockito.reset(mProcReader);
final long[][] times2 = increaseTime(times);
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times2));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times2[i], times[i])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length; i++) {
+ mCallback.verify(uids[i], getTotal(clusters, subtract(times2[i], times[i])));
}
+ mCallback.verifyNoMoreInteractions();
// Verify that there is no callback if any value in the proc file is -ve.
- Mockito.reset(mCallback, mBufferedReader);
+ mCallback.clear();
+ Mockito.reset(mProcReader);
final long[][] times3 = increaseTime(times2);
times3[uids.length - 1][cores - 1] *= -1;
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length-1;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times3[i], times2[i])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length - 1; i++) {
+ mCallback.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
}
- verifyNoMoreInteractions(mCallback);
+ mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified when the proc file had -ve value.
- Mockito.reset(mCallback, mBufferedReader);
- for(int i=0;i<cores;i++){
- times3[uids.length -1][i] = times2[uids.length -1][i] + uids[uids.length -1]*1000;
+ mCallback.clear();
+ Mockito.reset(mProcReader);
+ for (int i = 0; i < cores; i++) {
+ times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times3));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verify(mCallback, times(1)).onUidCpuPolicyTime(uids[uids.length-1], getTotal(cluster, subtract(times3[uids.length -1], times2[uids.length -1])));
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
+ mReader.readDelta(mCallback);
+ mCallback.verify(uids[uids.length - 1],
+ getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
- // // Verify that there is no callback if the values in the proc file are decreased.
- Mockito.reset(mCallback, mBufferedReader);
+ // Verify that there is no callback if the values in the proc file are decreased.
+ mCallback.clear();
+ Mockito.reset(mProcReader);
final long[][] times4 = increaseTime(times3);
- times4[uids.length - 1][cores - 1] = times3[uids.length - 1][cores - 1] - 1;
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times4));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- for (int i=0;i<uids.length-1;i++){
- verify(mCallback).onUidCpuPolicyTime(uids[i], getTotal(cluster, subtract(times4[i], times3[i])));
+ System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
+ times4[uids.length - 1][cores - 1] -= 100;
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
+ mReader.readDelta(mCallback);
+ for (int i = 0; i < uids.length - 1; i++) {
+ mCallback.verify(uids[i], getTotal(clusters, subtract(times4[i], times3[i])));
}
- verifyNoMoreInteractions(mCallback);
+ mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified when the proc file had decreased values.
- Mockito.reset(mCallback, mBufferedReader);
- for(int i=0;i<cores;i++){
- times4[uids.length -1][i] = times3[uids.length -1][i] + uids[uids.length -1]*1000;
+ mCallback.clear();
+ Mockito.reset(mProcReader);
+ for (int i = 0; i < cores; i++) {
+ times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
- when(mBufferedReader.readLine()).thenReturn(info, formatTime(uids, times4));
- mReader.readDeltaInternal(mBufferedReader, mCallback);
- verify(mCallback, times(1))
- .onUidCpuPolicyTime(uids[uids.length-1], getTotal(cluster, subtract(times3[uids.length -1], times2[uids.length -1])));
-
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
+ mReader.readDelta(mCallback);
+ mCallback.verify(uids[uids.length - 1],
+ getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
+ mCallback.verifyNoMoreInteractions();
}
@@ -195,26 +210,17 @@
return val;
}
- private String[] formatTime(int[] uids, long[][] times) {
- String[] lines = new String[uids.length + 1];
- for (int i=0;i<uids.length;i++){
- StringBuilder sb = new StringBuilder();
- sb.append(uids[i]).append(':');
- for(int j=0;j<times[i].length;j++){
- sb.append(' ').append(times[i][j]);
- }
- lines[i] = sb.toString();
- }
- lines[uids.length] = null;
- return lines;
- }
-
+ /**
+ * Unit is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3, ..., 10. So that when we
+ * divide shared cpu time by concurrent thread count, we always get a nice integer, avoiding
+ * rounding errors.
+ */
private long[][] increaseTime(long[][] original) {
long[][] newTime = new long[original.length][original[0].length];
Random rand = new Random();
- for(int i = 0;i<original.length;i++){
- for(int j=0;j<original[0].length;j++){
- newTime[i][j] = original[i][j] + rand.nextInt(1000_000) + 10000;
+ for (int i = 0; i < original.length; i++) {
+ for (int j = 0; j < original[0].length; j++) {
+ newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
}
}
return newTime;
@@ -223,23 +229,69 @@
// Format an array of cluster times according to the algorithm in KernelUidCpuClusterTimeReader
private long[] getTotal(int[] cluster, long[] times) {
int core = 0;
- long[] sum = new long[cluster.length];
- for(int i=0;i<cluster.length;i++){
- for(int j=0;j<cluster[i];j++){
- sum[i] += times[core++] * 10 / (j+1);
+ long[] sumTimes = new long[cluster.length];
+ for (int i = 0; i < cluster.length; i++) {
+ double sum = 0;
+ for (int j = 0; j < cluster[i]; j++) {
+ sum += (double) times[core++] * 10 / (j + 1);
}
+ sumTimes[i] = (long) sum;
}
- return sum;
+ return sumTimes;
}
- // Compare array1 against flattened 2d array array2 element by element
- private boolean testEqual(long[] array1, long[][] array2) {
- int k=0;
- for(int i=0;i<array2.length;i++){
- for(int j=0;j<array2[i].length;j++){
- if (k >= array1.length || array1[k++]!=array2[i][j])return false;
+ private class VerifiableCallback implements KernelUidCpuClusterTimeReader.Callback {
+
+ SparseArray<long[]> mData = new SparseArray<>();
+ int count = 0;
+
+ public void verify(int uid, long[] cpuClusterTimeMs) {
+ long[] array = mData.get(uid);
+ assertNotNull(array);
+ assertArrayEquals(cpuClusterTimeMs, array);
+ count++;
+ }
+
+ public void clear() {
+ mData.clear();
+ count = 0;
+ }
+
+ @Override
+ public void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs) {
+ long[] array = new long[cpuClusterTimeMs.length];
+ System.arraycopy(cpuClusterTimeMs, 0, array, 0, array.length);
+ mData.put(uid, array);
+ }
+
+ public void verifyNoMoreInteractions() {
+ assertEquals(mData.size(), count);
+ }
+ }
+
+ /**
+ * Format uids and times (in 10ms) into the following format:
+ * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
+ * uid1, time1a, time1b, ..., time1n,
+ * uid2, time2a, time2b, ..., time2n, etc.]
+ * where n is the number of policies
+ * xi is the number cpus on a particular policy
+ */
+ private ByteBuffer getUidTimesBytes(int[] uids, int[] clusters, long[][] times) {
+ int size = (1 + clusters.length + uids.length * (times[0].length + 1)) * 4;
+ ByteBuffer buf = ByteBuffer.allocate(size);
+ buf.order(ByteOrder.nativeOrder());
+ buf.putInt(clusters.length);
+ for (int i = 0; i < clusters.length; i++) {
+ buf.putInt(clusters[i]);
+ }
+ for (int i = 0; i < uids.length; i++) {
+ buf.putInt(uids[i]);
+ for (int j = 0; j < times[i].length; j++) {
+ buf.putInt((int) (times[i][j]));
}
}
- return k == array1.length;
+ buf.flip();
+ return buf.order(ByteOrder.nativeOrder());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
index 95b0b29..0950721 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
@@ -19,15 +19,15 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
import org.junit.Before;
import org.junit.Test;
@@ -37,6 +37,8 @@
import org.mockito.MockitoAnnotations;
import java.io.BufferedReader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.Arrays;
/**
@@ -50,9 +52,9 @@
*
* Build: m FrameworksCoreTests
* Install: adb install -r \
- * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
* Run: adb shell am instrument -e class com.android.internal.os.KernelUidCpuFreqTimeReaderTest -w \
- * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+ * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
*
* or
*
@@ -61,16 +63,22 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelUidCpuFreqTimeReaderTest {
- @Mock private BufferedReader mBufferedReader;
- @Mock private KernelUidCpuFreqTimeReader.Callback mCallback;
- @Mock private PowerProfile mPowerProfile;
+ @Mock
+ private BufferedReader mBufferedReader;
+ @Mock
+ private KernelUidCpuFreqTimeReader.Callback mCallback;
+ @Mock
+ private PowerProfile mPowerProfile;
+ @Mock
+ private KernelCpuProcReader mProcReader;
private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader();
+ mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader(mProcReader);
+ mKernelUidCpuFreqTimeReader.setThrottleInterval(0);
}
@Test
@@ -154,7 +162,7 @@
.thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, times));
mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback);
for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuFreqTime(uids[i], times[i]);
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i], times[i]);
}
verifyNoMoreInteractions(mCallback);
@@ -170,7 +178,7 @@
.thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, newTimes1));
mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback);
for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes1[i], times[i]));
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes1[i], times[i]));
}
verifyNoMoreInteractions(mCallback);
@@ -206,12 +214,89 @@
.thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, newTimes3));
mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback);
for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes3[i], newTimes2[i]));
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i],
+ subtract(newTimes3[i], newTimes2[i]));
}
verifyNoMoreInteractions(mCallback);
}
@Test
+ public void testReadDelta_Binary() throws Exception {
+ VerifiableCallback cb = new VerifiableCallback();
+ final long[] freqs = {110, 123, 145, 167, 289, 997};
+ final int[] uids = {1, 22, 333, 444, 555};
+ final long[][] times = new long[uids.length][freqs.length];
+ for (int i = 0; i < uids.length; ++i) {
+ for (int j = 0; j < freqs.length; ++j) {
+ times[i][j] = uids[i] * freqs[j] * 10;
+ }
+ }
+ when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs));
+ long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile);
+
+ assertArrayEquals(freqs, actualFreqs);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(cb);
+ for (int i = 0; i < uids.length; ++i) {
+ cb.verify(uids[i], times[i]);
+ }
+ cb.verifyNoMoreInteractions();
+
+ // Verify that a second call will only return deltas.
+ cb.clear();
+ Mockito.reset(mProcReader);
+ final long[][] newTimes1 = new long[uids.length][freqs.length];
+ for (int i = 0; i < uids.length; ++i) {
+ for (int j = 0; j < freqs.length; ++j) {
+ newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50;
+ }
+ }
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(cb);
+ for (int i = 0; i < uids.length; ++i) {
+ cb.verify(uids[i], subtract(newTimes1[i], times[i]));
+ }
+ cb.verifyNoMoreInteractions();
+
+ // Verify that there won't be a callback if the proc file values didn't change.
+ cb.clear();
+ Mockito.reset(mProcReader);
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(cb);
+ cb.verifyNoMoreInteractions();
+
+ // Verify that calling with a null callback doesn't result in any crashes
+ cb.clear();
+ Mockito.reset(mProcReader);
+ final long[][] newTimes2 = new long[uids.length][freqs.length];
+ for (int i = 0; i < uids.length; ++i) {
+ for (int j = 0; j < freqs.length; ++j) {
+ newTimes2[i][j] = newTimes1[i][j] + (uids[i] * freqs[j]) * 30;
+ }
+ }
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes2));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(null);
+ cb.verifyNoMoreInteractions();
+
+ // Verify that the readDelta call will only return deltas when
+ // the previous call had null callback.
+ cb.clear();
+ Mockito.reset(mProcReader);
+ final long[][] newTimes3 = new long[uids.length][freqs.length];
+ for (int i = 0; i < uids.length; ++i) {
+ for (int j = 0; j < freqs.length; ++j) {
+ newTimes3[i][j] = newTimes2[i][j] + (uids[i] + freqs[j]) * 40;
+ }
+ }
+ when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes3));
+ mKernelUidCpuFreqTimeReader.readDeltaBinary(cb);
+ for (int i = 0; i < uids.length; ++i) {
+ cb.verify(uids[i], subtract(newTimes3[i], newTimes2[i]));
+ }
+ cb.verifyNoMoreInteractions();
+ }
+
+ @Test
public void testReadDelta_malformedData() throws Exception {
final long[] freqs = {1, 12, 123, 1234, 12345, 123456};
final int[] uids = {1, 22, 333, 4444, 5555};
@@ -229,7 +314,7 @@
.thenReturn(getFreqsLine(freqs), getUidTimesLines(uids, times));
mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback);
for (int i = 0; i < uids.length; ++i) {
- verify(mCallback).onUidCpuFreqTime(uids[i], times[i]);
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i], times[i]);
}
verifyNoMoreInteractions(mCallback);
@@ -249,7 +334,7 @@
if (i == uids.length - 1) {
continue;
}
- verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes1[i], times[i]));
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes1[i], times[i]));
}
verifyNoMoreInteractions(mCallback);
@@ -280,7 +365,8 @@
if (i == uids.length - 1) {
continue;
}
- verify(mCallback).onUidCpuFreqTime(uids[i], subtract(newTimes2[i], newTimes1[i]));
+ Mockito.verify(mCallback).onUidCpuFreqTime(uids[i],
+ subtract(newTimes2[i], newTimes1[i]));
}
verifyNoMoreInteractions(mCallback);
@@ -327,6 +413,21 @@
return lines;
}
+ private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) {
+ int size = (1 + uids.length + uids.length * times[0].length) * 4;
+ ByteBuffer buf = ByteBuffer.allocate(size);
+ buf.order(ByteOrder.nativeOrder());
+ buf.putInt(times[0].length);
+ for (int i = 0; i < uids.length; i++) {
+ buf.putInt(uids[i]);
+ for (int j = 0; j < times[i].length; j++) {
+ buf.putInt((int) (times[i][j] / 10));
+ }
+ }
+ buf.flip();
+ return buf.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
+ }
+
private void setCpuClusterFreqs(int numClusters, int... clusterFreqs) {
assertEquals(numClusters, clusterFreqs.length);
when(mPowerProfile.getNumCpuClusters()).thenReturn(numClusters);
@@ -334,4 +435,33 @@
when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)).thenReturn(clusterFreqs[i]);
}
}
+
+ private class VerifiableCallback implements KernelUidCpuFreqTimeReader.Callback {
+
+ SparseArray<long[]> mData = new SparseArray<>();
+ int count = 0;
+
+ public void verify(int uid, long[] cpuFreqTimeMs) {
+ long[] array = mData.get(uid);
+ assertNotNull(array);
+ assertArrayEquals(cpuFreqTimeMs, array);
+ count++;
+ }
+
+ public void clear() {
+ mData.clear();
+ count = 0;
+ }
+
+ @Override
+ public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
+ long[] array = new long[cpuFreqTimeMs.length];
+ System.arraycopy(cpuFreqTimeMs, 0, array, 0, array.length);
+ mData.put(uid, array);
+ }
+
+ public void verifyNoMoreInteractions() {
+ assertEquals(mData.size(), count);
+ }
+ }
}
diff --git a/data/etc/Android.mk b/data/etc/Android.mk
index b2c6840..936ad22 100644
--- a/data/etc/Android.mk
+++ b/data/etc/Android.mk
@@ -39,3 +39,11 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
+
+########################
+include $(CLEAR_VARS)
+LOCAL_MODULE := hiddenapi-package-whitelist.xml
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
diff --git a/data/etc/hiddenapi-package-whitelist.xml b/data/etc/hiddenapi-package-whitelist.xml
new file mode 100644
index 0000000..54d8a23
--- /dev/null
+++ b/data/etc/hiddenapi-package-whitelist.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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
+ -->
+
+<!--
+This XML file declares which system apps should be exempted from the hidden API blacklisting, i.e.
+which apps should be allowed to access the entire private API.
+-->
+
+<config>
+ <hidden-api-whitelisted-app package="com.android.providers.contacts" />
+</config>
+
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 9d1fdbd..8e76dd3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -158,6 +158,7 @@
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.PACKAGE_USAGE_STATS"/>
<permission name="android.permission.PERFORM_CDMA_PROVISIONING"/>
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index a14609f..2797a4d 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -263,8 +263,16 @@
"Only able to copy windows with decor views");
}
Surface surface = null;
- if (source.peekDecorView().getViewRootImpl() != null) {
- surface = source.peekDecorView().getViewRootImpl().mSurface;
+ final ViewRootImpl root = source.peekDecorView().getViewRootImpl();
+ if (root != null) {
+ surface = root.mSurface;
+ final Rect surfaceInsets = root.mWindowAttributes.surfaceInsets;
+ if (srcRect == null) {
+ srcRect = new Rect(surfaceInsets.left, surfaceInsets.top,
+ root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top);
+ } else {
+ srcRect.offset(surfaceInsets.left, surfaceInsets.top);
+ }
}
if (surface == null || !surface.isValid()) {
throw new IllegalArgumentException(
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 3d879f5..86dfc9c 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -123,6 +123,10 @@
* A device type describing a USB audio headset.
*/
public static final int TYPE_USB_HEADSET = 22;
+ /**
+ * A device type describing a Hearing Aid.
+ */
+ public static final int TYPE_HEARING_AID = 23;
/** @hide */
@IntDef(flag = false, prefix = "TYPE", value = {
@@ -144,7 +148,8 @@
TYPE_FM,
TYPE_AUX_LINE,
TYPE_IP,
- TYPE_BUS }
+ TYPE_BUS,
+ TYPE_HEARING_AID }
)
@Retention(RetentionPolicy.SOURCE)
public @interface AudioDeviceTypeOut {}
@@ -171,6 +176,7 @@
case TYPE_AUX_LINE:
case TYPE_IP:
case TYPE_BUS:
+ case TYPE_HEARING_AID:
return true;
default:
return false;
@@ -367,6 +373,7 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_AUX_LINE, TYPE_AUX_LINE);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BUS, TYPE_BUS);
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HEARING_AID, TYPE_HEARING_AID);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -415,6 +422,7 @@
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_AUX_LINE, AudioSystem.DEVICE_OUT_AUX_LINE);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_OUT_BUS);
+ EXT_TO_INT_DEVICE_MAPPING.put(TYPE_HEARING_AID, AudioSystem.DEVICE_OUT_HEARING_AID);
}
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index be9fcb8..3885f90 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -401,6 +401,7 @@
public static final int DEVICE_OUT_BUS = 0x1000000;
public static final int DEVICE_OUT_PROXY = 0x2000000;
public static final int DEVICE_OUT_USB_HEADSET = 0x4000000;
+ public static final int DEVICE_OUT_HEARING_AID = 0x8000000;
public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
@@ -431,6 +432,7 @@
DEVICE_OUT_BUS |
DEVICE_OUT_PROXY |
DEVICE_OUT_USB_HEADSET |
+ DEVICE_OUT_HEARING_AID |
DEVICE_OUT_DEFAULT);
public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP |
DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
@@ -546,6 +548,7 @@
public static final String DEVICE_OUT_BUS_NAME = "bus";
public static final String DEVICE_OUT_PROXY_NAME = "proxy";
public static final String DEVICE_OUT_USB_HEADSET_NAME = "usb_headset";
+ public static final String DEVICE_OUT_HEARING_AID_NAME = "hearing_aid_out";
public static final String DEVICE_IN_COMMUNICATION_NAME = "communication";
public static final String DEVICE_IN_AMBIENT_NAME = "ambient";
@@ -628,6 +631,8 @@
return DEVICE_OUT_PROXY_NAME;
case DEVICE_OUT_USB_HEADSET:
return DEVICE_OUT_USB_HEADSET_NAME;
+ case DEVICE_OUT_HEARING_AID:
+ return DEVICE_OUT_HEARING_AID_NAME;
case DEVICE_OUT_DEFAULT:
default:
return Integer.toString(device);
diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java
index 231a4a5..e1d078d 100644
--- a/media/java/android/media/MediaBrowser2.java
+++ b/media/java/android/media/MediaBrowser2.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaSession2.ControllerInfo;
import android.media.update.ApiLoader;
import android.media.update.MediaBrowser2Provider;
import android.os.Bundle;
@@ -54,11 +56,18 @@
/**
* Called when there's change in the parent's children.
+ * <p>
+ * This API is called when the library service called
+ * {@link MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, int, Bundle)} or
+ * {@link MediaLibrarySession#notifyChildrenChanged(String, int, Bundle)} for the parent.
*
* @param parentId parent id that you've specified with {@link #subscribe(String, Bundle)}
- * @param extras extra bundle that you've specified with {@link #subscribe(String, Bundle)}
+ * @param childCount number of children
+ * @param extras extra bundle from the library service. Can be differ from extras that
+ * you've specified with {@link #subscribe(String, Bundle)}.
*/
- public void onChildrenChanged(@NonNull String parentId, @Nullable Bundle extras) { }
+ public void onChildrenChanged(@NonNull String parentId, int childCount,
+ @Nullable Bundle extras) { }
/**
* Called when the list of items has been returned by the library service for the previous
@@ -69,12 +78,11 @@
* {@link #getChildren(String, int, int, Bundle)}
* @param pageSize page size that you've specified with
* {@link #getChildren(String, int, int, Bundle)}
- * @param extras extra bundle that you've specified with
- * {@link #getChildren(String, int, int, Bundle)}
* @param result result. Can be {@code null}
+ * @param extras extra bundle from the library service
*/
public void onChildrenLoaded(@NonNull String parentId, int page, int pageSize,
- @Nullable Bundle extras, @Nullable List<MediaItem2> result) { }
+ @Nullable List<MediaItem2> result, @Nullable Bundle extras) { }
/**
* Called when the item has been returned by the library service for the previous
@@ -92,11 +100,11 @@
* {@link MediaBrowser2#search(String, Bundle)}.
*
* @param query search query that you've specified with {@link #search(String, Bundle)}
- * @param extras extra bundle
* @param itemCount The item count for the search result
+ * @param extras extra bundle from the library service
*/
- public void onSearchResultChanged(@NonNull String query, @Nullable Bundle extras,
- int itemCount) { }
+ public void onSearchResultChanged(@NonNull String query, int itemCount,
+ @Nullable Bundle extras) { }
/**
* Called when the search result has been returned by the library service for the previous
@@ -110,12 +118,11 @@
* {@link #getSearchResult(String, int, int, Bundle)}
* @param pageSize page size that you've specified with
* {@link #getSearchResult(String, int, int, Bundle)}
- * @param extras extra bundle that you've specified with
- * {@link #getSearchResult(String, int, int, Bundle)}
* @param result result. Can be {@code null}.
+ * @param extras extra bundle from the library service
*/
public void onSearchResultLoaded(@NonNull String query, int page, int pageSize,
- @Nullable Bundle extras, @Nullable List<MediaItem2> result) { }
+ @Nullable List<MediaItem2> result, @Nullable Bundle extras) { }
}
public MediaBrowser2(@NonNull Context context, @NonNull SessionToken2 token,
@@ -144,7 +151,7 @@
/**
* Subscribe to a parent id for the change in its children. When there's a change,
- * {@link BrowserCallback#onChildrenChanged(String, Bundle)} will be called with the bundle
+ * {@link BrowserCallback#onChildrenChanged(String, int, Bundle)} will be called with the bundle
* that you've specified. You should call {@link #getChildren(String, int, int, Bundle)} to get
* the actual contents for the parent.
*
@@ -158,17 +165,19 @@
/**
* Unsubscribe for changes to the children of the parent, which was previously subscribed with
* {@link #subscribe(String, Bundle)}.
+ * <p>
+ * This unsubscribes all previous subscription with the parent id, regardless of the extra
+ * that was previously sent to the library service.
*
* @param parentId parent id
- * @param extras extra bundle
*/
- public void unsubscribe(String parentId, @Nullable Bundle extras) {
- mProvider.unsubscribe_impl(parentId, extras);
+ public void unsubscribe(String parentId) {
+ mProvider.unsubscribe_impl(parentId);
}
/**
* Get list of children under the parent. Result would be sent back asynchronously with the
- * {@link BrowserCallback#onChildrenLoaded(String, int, int, Bundle, List)}.
+ * {@link BrowserCallback#onChildrenLoaded(String, int, int, List, Bundle)}.
*
* @param parentId parent id for getting the children.
* @param page page number to get the result. Starts from {@code 1}
@@ -191,7 +200,7 @@
/**
* Send a search request to the library service. When the search result is changed,
- * {@link BrowserCallback#onSearchResultChanged(String, Bundle, int)} will be called. You should
+ * {@link BrowserCallback#onSearchResultChanged(String, int, Bundle)} will be called. You should
* call {@link #getSearchResult(String, int, int, Bundle)} to get the actual search result.
*
* @param query search query. Should not be an empty string.
@@ -203,7 +212,7 @@
/**
* Get the search result from lhe library service. Result would be sent back asynchronously with
- * the {@link BrowserCallback#onSearchResultLoaded(String, int, int, Bundle, List)}.
+ * the {@link BrowserCallback#onSearchResultLoaded(String, int, int, List, Bundle)}.
*
* @param query search query that you've specified with {@link #search(String, Bundle)}
* @param page page number to get search result. Starts from {@code 1}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index bfd1fd4..6682e08 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -521,24 +521,6 @@
}
/**
- * Get the rating type supported by the session. One of:
- * <ul>
- * <li>{@link Rating2#RATING_NONE}</li>
- * <li>{@link Rating2#RATING_HEART}</li>
- * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
- * <li>{@link Rating2#RATING_3_STARS}</li>
- * <li>{@link Rating2#RATING_4_STARS}</li>
- * <li>{@link Rating2#RATING_5_STARS}</li>
- * <li>{@link Rating2#RATING_PERCENTAGE}</li>
- * </ul>
- *
- * @return The supported rating type
- */
- public int getRatingType() {
- return mProvider.getRatingType_impl();
- }
-
- /**
* Get an intent for launching UI associated with this session if one exists.
*
* @return A {@link PendingIntent} to launch UI or null.
@@ -571,7 +553,12 @@
/**
* Rate the media. This will cause the rating to be set for the current user.
- * The Rating type must match the type returned by {@link #getRatingType()}.
+ * The rating style must follow the user rating style from the session.
+ * You can get the rating style from the session through the
+ * {@link MediaMetadata#getRating(String)} with the key
+ * {@link MediaMetadata#METADATA_KEY_USER_RATING}.
+ * <p>
+ * If the user rating was {@code null}, the media item does not accept setting user rating.
*
* @param mediaId The id of the media
* @param rating The rating to set
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
index 95d83ee..ec37d0e 100644
--- a/media/java/android/media/MediaLibraryService2.java
+++ b/media/java/android/media/MediaLibraryService2.java
@@ -78,26 +78,36 @@
}
/**
- * Notify subscribed controller about change in a parent's children.
+ * Notify the controller of the change in a parent's children.
+ * <p>
+ * If the controller hasn't subscribed to the parent, the API will do nothing.
+ * <p>
+ * Controllers will use {@link MediaBrowser2#getChildren(String, int, int, Bundle)} to get
+ * the list of children.
*
* @param controller controller to notify
- * @param parentId
- * @param extras
+ * @param parentId parent id with changes in its children
+ * @param childCount number of children.
+ * @param extras extra information from session to controller
*/
public void notifyChildrenChanged(@NonNull ControllerInfo controller,
- @NonNull String parentId, @NonNull Bundle extras) {
- mProvider.notifyChildrenChanged_impl(controller, parentId, extras);
+ @NonNull String parentId, int childCount, @Nullable Bundle extras) {
+ mProvider.notifyChildrenChanged_impl(controller, parentId, childCount, extras);
}
/**
- * Notify subscribed controller about change in a parent's children.
+ * Notify all controllers that subscribed to the parent about change in the parent's
+ * children, regardless of the extra bundle supplied by
+ * {@link MediaBrowser2#subscribe(String, Bundle)}.
*
* @param parentId parent id
- * @param extras extra bundle
+ * @param childCount number of children
+ * @param extras extra information from session to controller
*/
// This is for the backward compatibility.
- public void notifyChildrenChanged(@NonNull String parentId, @Nullable Bundle extras) {
- mProvider.notifyChildrenChanged_impl(parentId, extras);
+ public void notifyChildrenChanged(@NonNull String parentId, int childCount,
+ @Nullable Bundle extras) {
+ mProvider.notifyChildrenChanged_impl(parentId, childCount, extras);
}
/**
@@ -105,12 +115,12 @@
*
* @param controller controller to notify
* @param query previously sent search query from the controller.
- * @param extras extra bundle
* @param itemCount the number of items that have been found in the search.
+ * @param extras extra bundle
*/
public void notifySearchResultChanged(@NonNull ControllerInfo controller,
- @NonNull String query, @NonNull Bundle extras, int itemCount) {
- mProvider.notifySearchResultChanged_impl(controller, query, extras, itemCount);
+ @NonNull String query, int itemCount, @NonNull Bundle extras) {
+ mProvider.notifySearchResultChanged_impl(controller, query, itemCount, extras);
}
}
@@ -176,25 +186,27 @@
}
/**
- * Called when a controller subscribes to the parent.
+ * Called when a controller subscribed to the parent.
+ * <p>
+ * It's your responsibility to keep subscriptions by your own and call
+ * {@link MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, Bundle)} when
+ * the parent is changed.
*
* @param controller controller
* @param parentId parent id
* @param extras extra bundle
*/
- public void onSubscribed(@NonNull ControllerInfo controller, String parentId,
+ public void onSubscribed(@NonNull ControllerInfo controller, @NonNull String parentId,
@Nullable Bundle extras) {
}
/**
- * Called when a controller unsubscribes to the parent.
+ * Called when a controller unsubscribed to the parent.
*
* @param controller controller
* @param parentId parent id
- * @param extras extra bundle
*/
- public void onUnsubscribed(@NonNull ControllerInfo controller, String parentId,
- @Nullable Bundle extras) {
+ public void onUnsubscribed(@NonNull ControllerInfo controller, @NonNull String parentId) {
}
/**
@@ -206,7 +218,6 @@
*/
public void onSearch(@NonNull ControllerInfo controllerInfo, @NonNull String query,
@Nullable Bundle extras) {
-
}
/**
@@ -252,11 +263,6 @@
}
@Override
- public MediaLibrarySessionBuilder setRatingType(int type) {
- return super.setRatingType(type);
- }
-
- @Override
public MediaLibrarySessionBuilder setSessionActivity(@Nullable PendingIntent pi) {
return super.setSessionActivity(pi);
}
diff --git a/media/java/android/media/MediaMetadata2.java b/media/java/android/media/MediaMetadata2.java
index 54a9057..fabf42b 100644
--- a/media/java/android/media/MediaMetadata2.java
+++ b/media/java/android/media/MediaMetadata2.java
@@ -436,6 +436,9 @@
/**
* Return a {@link Rating2} for the given key or null if no rating exists for
* the given key.
+ * <p>
+ * For the {@link #METADATA_KEY_USER_RATING}, A {@code null} return value means that user rating
+ * cannot be set by {@link MediaController2}.
*
* @param key The key the value is stored under
* @return A {@link Rating2} or {@code null}
diff --git a/media/java/android/media/MediaPlayerInterface.java b/media/java/android/media/MediaPlayerInterface.java
index 78e2391..b81c3d6 100644
--- a/media/java/android/media/MediaPlayerInterface.java
+++ b/media/java/android/media/MediaPlayerInterface.java
@@ -16,6 +16,7 @@
package android.media;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.MediaSession2.PlaylistParams;
@@ -29,13 +30,56 @@
*/
public interface MediaPlayerInterface {
/**
- * Listens change in {@link PlaybackState2}.
+ * Unspecified media player error.
*/
- interface PlaybackListener {
+ int MEDIA_ERROR_UNKNOWN = MediaPlayer2.MEDIA_ERROR_UNKNOWN;
+
+ /**
+ * The video is streamed and its container is not valid for progressive
+ * playback i.e the video's index (e.g moov atom) is not at the start of the
+ * file.
+ */
+ int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK =
+ MediaPlayer2.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK;
+
+ /**
+ * File or network related operation errors.
+ */
+ int MEDIA_ERROR_IO = MediaPlayer2.MEDIA_ERROR_IO;
+
+ /**
+ * Bitstream is not conforming to the related coding standard or file spec.
+ */
+ int MEDIA_ERROR_MALFORMED = MediaPlayer2.MEDIA_ERROR_MALFORMED;
+
+ /**
+ * Bitstream is conforming to the related coding standard or file spec, but
+ * the media framework does not support the feature.
+ */
+ int MEDIA_ERROR_UNSUPPORTED = MediaPlayer2.MEDIA_ERROR_UNSUPPORTED;
+
+ /**
+ * Some operation takes too long to complete, usually more than 3-5 seconds.
+ */
+ int MEDIA_ERROR_TIMED_OUT = MediaPlayer2.MEDIA_ERROR_TIMED_OUT;
+
+ /**
+ * Callbacks to listens to the changes in {@link PlaybackState2} and error.
+ */
+ interface EventCallback {
/**
* Called when {@link PlaybackState2} for this player is changed.
*/
- void onPlaybackChanged(PlaybackState2 state);
+ default void onPlaybackStateChanged(PlaybackState2 state) { }
+
+ /**
+ * Called to indicate an error.
+ *
+ * @param mediaId optional mediaId to indicate error
+ * @param what what
+ * @param extra
+ */
+ default void onError(@Nullable String mediaId, int what, int extra) { }
}
// Transport controls that session will send command directly to this player.
@@ -75,17 +119,18 @@
PlaylistParams getPlaylistParams();
/**
- * Add a {@link PlaybackListener} to be invoked when the playback state is changed.
+ * Register a {@link EventCallback}.
*
- * @param executor the Handler that will receive the listener
- * @param listener the listener that will be run
+ * @param executor a callback executor
+ * @param callback a EventCallback
*/
- void addPlaybackListener(Executor executor, PlaybackListener listener);
+ void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull EventCallback callback);
/**
- * Remove previously added {@link PlaybackListener}.
+ * Unregister previously registered {@link EventCallback}.
*
- * @param listener the listener to be removed
+ * @param callback a EventCallback
*/
- void removePlaybackListener(PlaybackListener listener);
+ void unregisterEventCallback(@NonNull EventCallback callback);
}
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 0258dca..63e4e65 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -24,7 +24,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.media.MediaPlayerInterface.PlaybackListener;
+import android.media.MediaPlayerInterface.EventCallback;
import android.media.session.MediaSession;
import android.media.session.MediaSession.Callback;
import android.media.session.PlaybackState;
@@ -41,7 +41,6 @@
import android.os.Handler;
import android.os.IInterface;
import android.os.ResultReceiver;
-import android.text.TextUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -87,8 +86,7 @@
// TODO(jaewan): Should we define IntDef? Currently we don't have to allow subclass to add more.
// TODO(jaewan): Shouldn't we pull out?
// TODO(jaewan): Should we also protect getters not related with metadata?
- // Getters are getRatingType(), getPlaybackState(), getSessionActivity(),
- // getPlaylistParams())
+ // Getters are getPlaybackState(), getSessionActivity(), getPlaylistParams()
// Next ID: 23
/**
* Command code for the custom command which can be defined by string action in the
@@ -454,6 +452,11 @@
/**
* Called when a controller set rating of a media item through
* {@link MediaController2#setRating(String, Rating2)}.
+ * <p>
+ * To allow setting user rating for a {@link MediaItem2}, the media item's metadata
+ * should have {@link Rating2} with the key {@link MediaMetadata#METADATA_KEY_USER_RATING},
+ * in order to provide possible rating style for controller. Controller will follow the
+ * rating style.
*
* @param controller controller information
* @param mediaId media id from the controller
@@ -621,24 +624,6 @@
}
/**
- * Set the style of rating used by this session. Apps trying to set the
- * rating should use this style. Must be one of the following:
- * <ul>
- * <li>{@link Rating2#RATING_NONE}</li>
- * <li>{@link Rating2#RATING_3_STARS}</li>
- * <li>{@link Rating2#RATING_4_STARS}</li>
- * <li>{@link Rating2#RATING_5_STARS}</li>
- * <li>{@link Rating2#RATING_HEART}</li>
- * <li>{@link Rating2#RATING_PERCENTAGE}</li>
- * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
- * </ul>
- */
- U setRatingType(@Rating2.Style int type) {
- mProvider.setRatingType_impl(type);
- return (U) this;
- }
-
- /**
* Set an intent for launching UI for this Session. This can be used as a
* quick link to an ongoing media screen. The intent should be for an
* activity that may be started using {@link Context#startActivity(Intent)}.
@@ -710,11 +695,6 @@
}
@Override
- public Builder setRatingType(@Rating2.Style int type) {
- return super.setRatingType(type);
- }
-
- @Override
public Builder setSessionActivity(@Nullable PendingIntent pi) {
return super.setSessionActivity(pi);
}
@@ -1077,8 +1057,9 @@
* to. Events from the {@link MediaController2} will be sent directly to the underlying
* player on the {@link Handler} where the session is created on.
* <p>
- * If the new player is successfully set, {@link PlaybackListener}
- * will be called to tell the current playback state of the new player.
+ * If the new player is successfully set,
+ * {@link EventCallback#onPlaybackStateChanged(PlaybackState2)} will be called to tell the
+ * current playback state of the new player.
* <p>
* For the remote playback case which you want to handle volume by yourself, use
* {@link #setPlayer(MediaPlayerInterface, VolumeProvider2)}.
@@ -1344,28 +1325,28 @@
}
/*
- * Add a {@link PlaybackListener} to listen changes in the underlying
- * {@link MediaPlayerInterface}. Listener will be called immediately to tell the current value.
+ * Register {@link EventCallback} to listen changes in the underlying
+ * {@link MediaPlayerInterface}, regardless of the change in the underlying player.
* <p>
- * Added listeners will be also called when the underlying player is changed.
+ * Registered callbacks will be also called when the underlying player is changed.
*
- * @param executor the call listener
- * @param listener the listener that will be run
- * @throws IllegalArgumentException when either the listener or handler is {@code null}.
+ * @param executor a callback Executor
+ * @param callback a EventCallback
+ * @throws IllegalArgumentException if executor or callback is {@code null}.
*/
- public void addPlaybackListener(@NonNull @CallbackExecutor Executor executor,
- @NonNull PlaybackListener listener) {
- mProvider.addPlaybackListener_impl(executor, listener);
+ public void registerPlayerEventCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull EventCallback callback) {
+ mProvider.registerPlayerEventCallback_impl(executor, callback);
}
/**
- * Remove previously added {@link PlaybackListener}.
+ * Unregister the previously registered {@link EventCallback}.
*
- * @param listener the listener to be removed
- * @throws IllegalArgumentException if the listener is {@code null}.
+ * @param callback the callback to be removed
+ * @throws IllegalArgumentException if the callback is {@code null}.
*/
- public void removePlaybackListener(@NonNull PlaybackListener listener) {
- mProvider.removePlaybackListener_impl(listener);
+ public void unregisterPlayerEventCallback(@NonNull EventCallback callback) {
+ mProvider.unregisterPlayerEventCallback_impl(callback);
}
/**
diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java
index 627974a..a95b8f2 100644
--- a/media/java/android/media/PlaybackState2.java
+++ b/media/java/android/media/PlaybackState2.java
@@ -34,7 +34,7 @@
* @hide
*/
public final class PlaybackState2 {
- // Similar to the PlaybackState2 with following changes
+ // Similar to the PlaybackState with following changes
// - Not implement Parcelable and added from/toBundle()
// - Removed playback state that doesn't match with the MediaPlayer2
// Full list should be finalized when the MediaPlayer2 has getter for the playback state.
@@ -73,6 +73,7 @@
// | EventCallback.onBufferingUpdate() | |
// +----------------------------------------+----------------------------------------+
// - Removed actions and custom actions.
+ // - Removed error string
// - Repeat mode / shuffle mode is now in the PlaylistParams
// TODO(jaewan): Replace states from MediaPlayer2
/**
@@ -110,8 +111,7 @@
public final static int STATE_BUFFERING = 4;
/**
- * State indicating this item is currently in an error state. The error
- * message should also be set when entering this state.
+ * State indicating this item is currently in an error state.
*/
public final static int STATE_ERROR = 5;
@@ -122,13 +122,10 @@
private final PlaybackState2Provider mProvider;
- // TODO(jaewan): Better error handling?
- // E.g. media item at #2 has issue, but continue playing #3
- // login error. fire intent xxx to login
public PlaybackState2(@NonNull Context context, int state, long position, long updateTime,
- float speed, long bufferedPosition, long activeItemId, CharSequence error) {
+ float speed, long bufferedPosition, long activeItemId) {
mProvider = ApiLoader.getProvider(context).createPlaybackState2(context, this, state,
- position, updateTime, speed, bufferedPosition, activeItemId, error);
+ position, updateTime, speed, bufferedPosition, activeItemId);
}
@Override
@@ -141,10 +138,8 @@
* <ul>
* <li> {@link PlaybackState2#STATE_NONE}</li>
* <li> {@link PlaybackState2#STATE_STOPPED}</li>
- * <li> {@link PlaybackState2#STATE_PREPARED}</li>
* <li> {@link PlaybackState2#STATE_PAUSED}</li>
* <li> {@link PlaybackState2#STATE_PLAYING}</li>
- * <li> {@link PlaybackState2#STATE_FINISH}</li>
* <li> {@link PlaybackState2#STATE_BUFFERING}</li>
* <li> {@link PlaybackState2#STATE_ERROR}</li>
* </ul>
@@ -182,14 +177,6 @@
}
/**
- * Get a user readable error message. This should be set when the state is
- * {@link PlaybackState2#STATE_ERROR}.
- */
- public CharSequence getErrorMessage() {
- return mProvider.getErrorMessage_impl();
- }
-
- /**
* Get the elapsed real time at which position was last updated. If the
* position has never been set this will return 0;
*
diff --git a/media/java/android/media/SessionPlayer2.java b/media/java/android/media/SessionPlayer2.java
deleted file mode 100644
index 60acf16..0000000
--- a/media/java/android/media/SessionPlayer2.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.SystemApi;
-import android.content.Context;
-import android.media.MediaSession2.PlaylistParams;
-import android.media.update.ApiLoader;
-import android.media.update.SessionPlayer2Provider;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Implementation of the {@link MediaPlayerInterface} which is backed by the {@link MediaPlayer2}
- * @hide
- */
-public class SessionPlayer2 implements MediaPlayerInterface {
- private final SessionPlayer2Provider mProvider;
-
- public SessionPlayer2(Context context) {
- mProvider = ApiLoader.getProvider(context).createSessionPlayer2(context, this);
- }
-
- @Override
- public void play() {
- mProvider.play_impl();
- }
-
- @Override
- public void prepare() {
- mProvider.prepare_impl();
- }
-
- @Override
- public void pause() {
- mProvider.pause_impl();
- }
-
- @Override
- public void stop() {
- mProvider.stop_impl();
- }
-
- @Override
- public void skipToPrevious() {
- mProvider.skipToPrevious_impl();
- }
-
- @Override
- public void skipToNext() {
- mProvider.skipToNext_impl();
- }
-
- @Override
- public void seekTo(long pos) {
- mProvider.seekTo_impl(pos);
- }
-
- @Override
- public void fastForward() {
- mProvider.fastForward_impl();
- }
-
- @Override
- public void rewind() {
- mProvider.rewind_impl();
- }
-
- @Override
- public PlaybackState2 getPlaybackState() {
- return mProvider.getPlaybackState_impl();
- }
-
- @Override
- public void setAudioAttributes(AudioAttributes attributes) {
- mProvider.setAudioAttributes_impl(attributes);
- }
-
- @Override
- public AudioAttributes getAudioAttributes() {
- return mProvider.getAudioAttributes_impl();
- }
-
- @Override
- public void setPlaylist(List<MediaItem2> playlist) {
- mProvider.setPlaylist_impl(playlist);
- }
-
- @Override
- public List<MediaItem2> getPlaylist() {
- return mProvider.getPlaylist_impl();
- }
-
- @Override
- public void setCurrentPlaylistItem(int index) {
- mProvider.setCurrentPlaylistItem_impl(index);
- }
-
- @Override
- public void setPlaylistParams(PlaylistParams params) {
- mProvider.setPlaylistParams_impl(params);
- }
-
- @Override
- public void addPlaylistItem(int index, MediaItem2 item) {
- mProvider.addPlaylistItem_impl(index, item);
- }
-
- @Override
- public void removePlaylistItem(MediaItem2 item) {
- mProvider.removePlaylistItem_impl(item);
- }
-
- @Override
- public PlaylistParams getPlaylistParams() {
- return mProvider.getPlaylistParams_impl();
- }
-
- @Override
- public void addPlaybackListener(Executor executor, PlaybackListener listener) {
- mProvider.addPlaybackListener_impl(executor, listener);
- }
-
- @Override
- public void removePlaybackListener(PlaybackListener listener) {
- mProvider.removePlaybackListener_impl(listener);
- }
-
- public MediaPlayer2 getPlayer() {
- return mProvider.getPlayer_impl();
- }
-
- @SystemApi
- public SessionPlayer2Provider getProvider() {
- return mProvider;
- }
-}
diff --git a/media/java/android/media/update/MediaBrowser2Provider.java b/media/java/android/media/update/MediaBrowser2Provider.java
index eda4c7c..a18701e 100644
--- a/media/java/android/media/update/MediaBrowser2Provider.java
+++ b/media/java/android/media/update/MediaBrowser2Provider.java
@@ -25,7 +25,7 @@
void getLibraryRoot_impl(Bundle rootHints);
void subscribe_impl(String parentId, Bundle extras);
- void unsubscribe_impl(String parentId, Bundle extras);
+ void unsubscribe_impl(String parentId);
void getItem_impl(String mediaId);
void getChildren_impl(String parentId, int page, int pageSize, Bundle extras);
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index 738bd0c..8d9efd5 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -43,7 +43,6 @@
boolean isConnected_impl();
PendingIntent getSessionActivity_impl();
- int getRatingType_impl();
void setVolumeTo_impl(int value, int flags);
void adjustVolume_impl(int direction, int flags);
diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
index d837833..5b5d36e 100644
--- a/media/java/android/media/update/MediaLibraryService2Provider.java
+++ b/media/java/android/media/update/MediaLibraryService2Provider.java
@@ -30,10 +30,11 @@
// Nothing new for now
interface MediaLibrarySessionProvider extends MediaSession2Provider {
- void notifyChildrenChanged_impl(ControllerInfo controller, String parentId, Bundle extras);
- void notifyChildrenChanged_impl(String parentId, Bundle extras);
- void notifySearchResultChanged_impl(ControllerInfo controller, String query, Bundle extras,
- int itemCount);
+ void notifyChildrenChanged_impl(ControllerInfo controller, String parentId,
+ int childCount, Bundle extras);
+ void notifyChildrenChanged_impl(String parentId, int childCount, Bundle extras);
+ void notifySearchResultChanged_impl(ControllerInfo controller, String query, int itemCount,
+ Bundle extras);
}
interface LibraryRootProvider {
diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java
index 41162e0..fc1f671 100644
--- a/media/java/android/media/update/MediaSession2Provider.java
+++ b/media/java/android/media/update/MediaSession2Provider.java
@@ -20,7 +20,7 @@
import android.media.MediaItem2;
import android.media.MediaMetadata2;
import android.media.MediaPlayerInterface;
-import android.media.MediaPlayerInterface.PlaybackListener;
+import android.media.MediaPlayerInterface.EventCallback;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
@@ -61,8 +61,8 @@
void setPlaylistParams_impl(PlaylistParams params);
PlaylistParams getPlaylistParams_impl();
- void addPlaybackListener_impl(Executor executor, PlaybackListener listener);
- void removePlaybackListener_impl(PlaybackListener listener);
+ void registerPlayerEventCallback_impl(Executor executor, EventCallback callback);
+ void unregisterPlayerEventCallback_impl(EventCallback callback);
interface CommandProvider {
int getCommandCode_impl();
@@ -117,7 +117,6 @@
interface BuilderBaseProvider<T extends MediaSession2, C extends SessionCallback> {
void setVolumeProvider_impl(VolumeProvider2 volumeProvider);
- void setRatingType_impl(int type);
void setSessionActivity_impl(PendingIntent pi);
void setId_impl(String id);
void setSessionCallback_impl(Executor executor, C callback);
diff --git a/media/java/android/media/update/PlaybackState2Provider.java b/media/java/android/media/update/PlaybackState2Provider.java
index 2875e98..93f769c 100644
--- a/media/java/android/media/update/PlaybackState2Provider.java
+++ b/media/java/android/media/update/PlaybackState2Provider.java
@@ -33,8 +33,6 @@
float getPlaybackSpeed_impl();
- CharSequence getErrorMessage_impl();
-
long getLastPositionUpdateTime_impl();
long getCurrentPlaylistItemIndex_impl();
diff --git a/media/java/android/media/update/SessionPlayer2Provider.java b/media/java/android/media/update/SessionPlayer2Provider.java
deleted file mode 100644
index e068c21..0000000
--- a/media/java/android/media/update/SessionPlayer2Provider.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.update;
-
-import android.media.AudioAttributes;
-import android.media.MediaItem2;
-import android.media.MediaPlayer2;
-import android.media.MediaPlayerInterface.PlaybackListener;
-import android.media.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- */
-public interface SessionPlayer2Provider {
- void play_impl();
- void prepare_impl();
- void pause_impl();
- void stop_impl();
- void skipToPrevious_impl();
- void skipToNext_impl();
- void seekTo_impl(long pos);
- void fastForward_impl();
- void rewind_impl();
- PlaybackState2 getPlaybackState_impl();
- void setAudioAttributes_impl(AudioAttributes attributes);
- AudioAttributes getAudioAttributes_impl();
- void addPlaylistItem_impl(int index, MediaItem2 item);
- void removePlaylistItem_impl(MediaItem2 item);
- void setPlaylist_impl(List<MediaItem2> playlist);
- List<MediaItem2> getPlaylist_impl();
- void setCurrentPlaylistItem_impl(int index);
- void setPlaylistParams_impl(PlaylistParams params);
- PlaylistParams getPlaylistParams_impl();
- void addPlaybackListener_impl(Executor executor, PlaybackListener listener);
- void removePlaybackListener_impl(PlaybackListener listener);
- MediaPlayer2 getPlayer_impl();
-}
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 57f04cc..29a30343 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -40,7 +40,6 @@
import android.media.MediaSessionService2.MediaNotification;
import android.media.PlaybackState2;
import android.media.Rating2;
-import android.media.SessionPlayer2;
import android.media.SessionToken2;
import android.media.VolumeProvider2;
import android.media.update.MediaLibraryService2Provider.LibraryRootProvider;
@@ -113,8 +112,6 @@
String packageName, String serviceName, int uid);
SessionToken2 SessionToken2_fromBundle(Context context, Bundle bundle);
- SessionPlayer2Provider createSessionPlayer2(Context context, SessionPlayer2 instance);
-
MediaItem2Provider createMediaItem2(Context context, MediaItem2 mediaItem2,
String mediaId, DataSourceDesc dsd, MediaMetadata2 metadata, int flags);
MediaItem2 fromBundle_MediaItem2(Context context, Bundle bundle);
@@ -136,7 +133,6 @@
Rating2 newPercentageRating_Rating2(Context context, float percent);
PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance, int state,
- long position, long updateTime, float speed, long bufferedPosition, long activeItemId,
- CharSequence error);
+ long position, long updateTime, float speed, long bufferedPosition, long activeItemId);
PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle);
}
diff --git a/native/webview/plat_support/Android.mk b/native/webview/plat_support/Android.mk
new file mode 100644
index 0000000..6a33fe2
--- /dev/null
+++ b/native/webview/plat_support/Android.mk
@@ -0,0 +1,52 @@
+#
+# Copyright (C) 2012 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.
+#
+
+# This package provides the system interfaces allowing WebView to render.
+
+LOCAL_PATH := $(call my-dir)
+
+# Native support library (libwebviewchromium_plat_support.so) - does NOT link
+# any native chromium code.
+include $(CLEAR_VARS)
+
+LOCAL_MODULE:= libwebviewchromium_plat_support
+
+LOCAL_SRC_FILES:= \
+ draw_gl_functor.cpp \
+ jni_entry_point.cpp \
+ graphics_utils.cpp \
+ graphic_buffer_impl.cpp \
+
+LOCAL_C_INCLUDES:= \
+ external/skia/include/core \
+ frameworks/base/core/jni/android/graphics \
+ frameworks/native/include/ui \
+
+LOCAL_SHARED_LIBRARIES += \
+ libandroid_runtime \
+ liblog \
+ libcutils \
+ libui \
+ libutils \
+ libhwui \
+ libandroidfw
+
+LOCAL_MODULE_TAGS := optional
+
+# To remove warnings from skia header files
+LOCAL_CFLAGS := -Wno-unused-parameter
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/native/webview/plat_support/LICENSE b/native/webview/plat_support/LICENSE
new file mode 100644
index 0000000..972bb2e
--- /dev/null
+++ b/native/webview/plat_support/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/native/webview/plat_support/draw_gl.h b/native/webview/plat_support/draw_gl.h
new file mode 100644
index 0000000..c8434b6
--- /dev/null
+++ b/native/webview/plat_support/draw_gl.h
@@ -0,0 +1,131 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+//******************************************************************************
+// This is a copy of the coresponding android_webview/public/browser header.
+// Any changes to the interface should be made there.
+//
+// The purpose of having the copy is twofold:
+// - it removes the need to have Chromium sources present in the tree in order
+// to build the plat_support library,
+// - it captures API that the corresponding Android release supports.
+//******************************************************************************
+
+#ifndef ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_GL_H_
+#define ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_GL_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// 1 is L/L MR1
+//
+// 2 starts at M, and added an imperfect workaround for complex clipping by
+// elevating the WebView into an FBO layer. If any transform, clip, or outline
+// clip occurs that would either likely use the stencil buffer for clipping, or
+// require shader based clipping in HWUI, the WebView is drawn into an FBO (if
+// it fits).
+// This is a temporary workaround for a lack of WebView support for stencil/
+// shader based round rect clipping, and should be removed when webview is
+// capable of supporting these clips internally when drawing.
+//
+// 3 starts during development of P, when android defaults from HWUI to skia as
+// the GL renderer. Skia already maintains and restores its GL state, so there
+// is no need for WebView to restore this state. Skia also no longer promises
+// GL state on entering draw, such as no vertex array buffer binding.
+static const int kAwDrawGLInfoVersion = 3;
+
+// Holds the information required to trigger an OpenGL drawing operation.
+struct AwDrawGLInfo {
+ int version; // The AwDrawGLInfo this struct was built with.
+
+ // Input: tells the draw function what action to perform.
+ enum Mode {
+ kModeDraw = 0,
+ kModeProcess,
+ kModeProcessNoContext,
+ kModeSync,
+ } mode;
+
+ // Input: current clip rect in surface coordinates. Reflects the current state
+ // of the OpenGL scissor rect. Both the OpenGL scissor rect and viewport are
+ // set by the caller of the draw function and updated during View animations.
+ int clip_left;
+ int clip_top;
+ int clip_right;
+ int clip_bottom;
+
+ // Input: current width/height of destination surface.
+ int width;
+ int height;
+
+ // Input: is the View rendered into an independent layer.
+ // If false, the surface is likely to hold to the full screen contents, with
+ // the scissor box set by the caller to the actual View location and size.
+ // Also the transformation matrix will contain at least a translation to the
+ // position of the View to render, plus any other transformations required as
+ // part of any ongoing View animation. View translucency (alpha) is ignored,
+ // although the framework will set is_layer to true for non-opaque cases.
+ // Can be requested via the View.setLayerType(View.LAYER_TYPE_NONE, ...)
+ // Android API method.
+ //
+ // If true, the surface is dedicated to the View and should have its size.
+ // The viewport and scissor box are set by the caller to the whole surface.
+ // Animation transformations are handled by the caller and not reflected in
+ // the provided transformation matrix. Translucency works normally.
+ // Can be requested via the View.setLayerType(View.LAYER_TYPE_HARDWARE, ...)
+ // Android API method.
+ bool is_layer;
+
+ // Input: current transformation matrix in surface pixels.
+ // Uses the column-based OpenGL matrix format.
+ float transform[16];
+};
+
+// Function to invoke a direct GL draw into the client's pre-configured
+// GL context. Obtained via AwContents.getDrawGLFunction() (static).
+// |view_context| is an opaque identifier that was returned by the corresponding
+// call to AwContents.getAwDrawGLViewContext().
+// |draw_info| carries the in and out parameters for this draw.
+// |spare| ignored; pass NULL.
+typedef void (AwDrawGLFunction)(long view_context,
+ AwDrawGLInfo* draw_info,
+ void* spare);
+enum AwMapMode {
+ MAP_READ_ONLY,
+ MAP_WRITE_ONLY,
+ MAP_READ_WRITE,
+};
+
+// Called to create a GraphicBuffer
+typedef long AwCreateGraphicBufferFunction(int w, int h);
+// Called to release a GraphicBuffer
+typedef void AwReleaseGraphicBufferFunction(long buffer_id);
+// Called to map a GraphicBuffer in |mode|.
+typedef int AwMapFunction(long buffer_id, AwMapMode mode, void** vaddr);
+// Called to unmap a GraphicBuffer
+typedef int AwUnmapFunction(long buffer_id);
+// Called to get a native buffer pointer
+typedef void* AwGetNativeBufferFunction(long buffer_id);
+// Called to get the stride of the buffer
+typedef unsigned int AwGetStrideFunction(long buffer_id);
+
+static const int kAwDrawGLFunctionTableVersion = 1;
+
+// Set of functions used in rendering in hardware mode
+struct AwDrawGLFunctionTable {
+ int version;
+ AwCreateGraphicBufferFunction* create_graphic_buffer;
+ AwReleaseGraphicBufferFunction* release_graphic_buffer;
+ AwMapFunction* map;
+ AwUnmapFunction* unmap;
+ AwGetNativeBufferFunction* get_native_buffer;
+ AwGetStrideFunction* get_stride;
+};
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_GL_H_
diff --git a/native/webview/plat_support/draw_gl_functor.cpp b/native/webview/plat_support/draw_gl_functor.cpp
new file mode 100644
index 0000000..d54f558
--- /dev/null
+++ b/native/webview/plat_support/draw_gl_functor.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+// Provides a webviewchromium glue layer adapter from the internal Android
+// GL Functor data types into the types the chromium stack expects, and back.
+
+#define LOG_TAG "webviewchromium_plat_support"
+
+#include "draw_gl.h"
+
+#include <Properties.h>
+#include <errno.h>
+#include <jni.h>
+#include <private/hwui/DrawGlInfo.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <utils/Functor.h>
+#include <utils/Log.h>
+
+#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
+#define COMPILE_ASSERT(expr, err) \
+__unused static const char (err)[(expr) ? 1 : -1] = "";
+
+namespace android {
+namespace {
+
+AwDrawGLFunction* g_aw_drawgl_function = NULL;
+
+class DrawGLFunctor : public Functor {
+ public:
+ explicit DrawGLFunctor(jlong view_context) : view_context_(view_context) {}
+ virtual ~DrawGLFunctor() {}
+
+ // Functor
+ virtual status_t operator ()(int what, void* data) {
+ using uirenderer::DrawGlInfo;
+ if (!g_aw_drawgl_function) {
+ ALOGE("Cannot draw: no DrawGL Function installed");
+ return DrawGlInfo::kStatusDone;
+ }
+
+ AwDrawGLInfo aw_info;
+ // TODO(boliu): Remove property check once OpenGL fallback is removed.
+ auto render_pipeline_type =
+ android::uirenderer::Properties::getRenderPipelineType();
+ aw_info.version = (render_pipeline_type ==
+ android::uirenderer::RenderPipelineType::OpenGL)
+ ? 2
+ : kAwDrawGLInfoVersion;
+ switch (what) {
+ case DrawGlInfo::kModeDraw: {
+ aw_info.mode = AwDrawGLInfo::kModeDraw;
+ DrawGlInfo* gl_info = reinterpret_cast<DrawGlInfo*>(data);
+
+ // Map across the input values.
+ aw_info.clip_left = gl_info->clipLeft;
+ aw_info.clip_top = gl_info->clipTop;
+ aw_info.clip_right = gl_info->clipRight;
+ aw_info.clip_bottom = gl_info->clipBottom;
+ aw_info.width = gl_info->width;
+ aw_info.height = gl_info->height;
+ aw_info.is_layer = gl_info->isLayer;
+ COMPILE_ASSERT(NELEM(aw_info.transform) == NELEM(gl_info->transform),
+ mismatched_transform_matrix_sizes);
+ for (int i = 0; i < NELEM(aw_info.transform); ++i) {
+ aw_info.transform[i] = gl_info->transform[i];
+ }
+ break;
+ }
+ case DrawGlInfo::kModeProcess:
+ aw_info.mode = AwDrawGLInfo::kModeProcess;
+ break;
+ case DrawGlInfo::kModeProcessNoContext:
+ aw_info.mode = AwDrawGLInfo::kModeProcessNoContext;
+ break;
+ case DrawGlInfo::kModeSync:
+ aw_info.mode = AwDrawGLInfo::kModeSync;
+ break;
+ default:
+ ALOGE("Unexpected DrawGLInfo type %d", what);
+ return DrawGlInfo::kStatusDone;
+ }
+
+ // Invoke the DrawGL method.
+ g_aw_drawgl_function(view_context_, &aw_info, NULL);
+
+ return DrawGlInfo::kStatusDone;
+ }
+
+ private:
+ intptr_t view_context_;
+};
+
+// Raise the file handle soft limit to the hard limit since gralloc buffers
+// uses file handles.
+void RaiseFileNumberLimit() {
+ static bool have_raised_limit = false;
+ if (have_raised_limit)
+ return;
+
+ have_raised_limit = true;
+ struct rlimit limit_struct;
+ limit_struct.rlim_cur = 0;
+ limit_struct.rlim_max = 0;
+ if (getrlimit(RLIMIT_NOFILE, &limit_struct) == 0) {
+ limit_struct.rlim_cur = limit_struct.rlim_max;
+ if (setrlimit(RLIMIT_NOFILE, &limit_struct) != 0) {
+ ALOGE("setrlimit failed: %s", strerror(errno));
+ }
+ } else {
+ ALOGE("getrlimit failed: %s", strerror(errno));
+ }
+}
+
+jlong CreateGLFunctor(JNIEnv*, jclass, jlong view_context) {
+ RaiseFileNumberLimit();
+ return reinterpret_cast<jlong>(new DrawGLFunctor(view_context));
+}
+
+void DestroyGLFunctor(JNIEnv*, jclass, jlong functor) {
+ delete reinterpret_cast<DrawGLFunctor*>(functor);
+}
+
+void SetChromiumAwDrawGLFunction(JNIEnv*, jclass, jlong draw_function) {
+ g_aw_drawgl_function = reinterpret_cast<AwDrawGLFunction*>(draw_function);
+}
+
+const char kClassName[] = "com/android/webview/chromium/DrawGLFunctor";
+const JNINativeMethod kJniMethods[] = {
+ { "nativeCreateGLFunctor", "(J)J",
+ reinterpret_cast<void*>(CreateGLFunctor) },
+ { "nativeDestroyGLFunctor", "(J)V",
+ reinterpret_cast<void*>(DestroyGLFunctor) },
+ { "nativeSetChromiumAwDrawGLFunction", "(J)V",
+ reinterpret_cast<void*>(SetChromiumAwDrawGLFunction) },
+};
+
+} // namespace
+
+void RegisterDrawGLFunctor(JNIEnv* env) {
+ jclass clazz = env->FindClass(kClassName);
+ LOG_ALWAYS_FATAL_IF(!clazz, "Unable to find class '%s'", kClassName);
+
+ int res = env->RegisterNatives(clazz, kJniMethods, NELEM(kJniMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "register native methods failed: res=%d", res);
+}
+
+} // namespace android
diff --git a/native/webview/plat_support/draw_sw.h b/native/webview/plat_support/draw_sw.h
new file mode 100644
index 0000000..7423e13
--- /dev/null
+++ b/native/webview/plat_support/draw_sw.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+//******************************************************************************
+// This is a copy of the coresponding android_webview/public/browser header.
+// Any changes to the interface should be made there.
+//
+// The purpose of having the copy is twofold:
+// - it removes the need to have Chromium sources present in the tree in order
+// to build the plat_support library,
+// - it captures API that the corresponding Android release supports.
+//******************************************************************************
+
+#ifndef ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_SW_H_
+#define ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_SW_H_
+
+#include <jni.h>
+#include <stddef.h>
+
+#ifndef __cplusplus
+#error "Can't mix C and C++ when using jni.h"
+#endif
+
+class SkCanvasState;
+class SkPicture;
+
+static const int kAwPixelInfoVersion = 3;
+
+// Holds the information required to implement the SW draw to system canvas.
+struct AwPixelInfo {
+ int version; // The kAwPixelInfoVersion this struct was built with.
+ SkCanvasState* state; // The externalize state in skia format.
+ // NOTE: If you add more members, bump kAwPixelInfoVersion.
+};
+
+// Function that can be called to fish out the underlying native pixel data
+// from a Java canvas object, for optimized rendering path.
+// Returns the pixel info on success, which must be freed via a call to
+// AwReleasePixelsFunction, or NULL.
+typedef AwPixelInfo* (AwAccessPixelsFunction)(JNIEnv* env, jobject canvas);
+
+// Must be called to balance every *successful* call to AwAccessPixelsFunction
+// (i.e. that returned true).
+typedef void (AwReleasePixelsFunction)(AwPixelInfo* pixels);
+
+// Called to create an Android Picture object encapsulating a native SkPicture.
+typedef jobject (AwCreatePictureFunction)(JNIEnv* env, SkPicture* picture);
+
+// Method that returns the current Skia function.
+typedef void (SkiaVersionFunction)(int* major, int* minor, int* patch);
+
+// Called to verify if the Skia versions are compatible.
+typedef bool (AwIsSkiaVersionCompatibleFunction)(SkiaVersionFunction function);
+
+static const int kAwDrawSWFunctionTableVersion = 1;
+
+// "vtable" for the functions declared in this file. An instance must be set via
+// AwContents.setAwDrawSWFunctionTable
+struct AwDrawSWFunctionTable {
+ int version;
+ AwAccessPixelsFunction* access_pixels;
+ AwReleasePixelsFunction* release_pixels;
+};
+
+#endif // ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_SW_H_
diff --git a/native/webview/plat_support/graphic_buffer_impl.cpp b/native/webview/plat_support/graphic_buffer_impl.cpp
new file mode 100644
index 0000000..4426778
--- /dev/null
+++ b/native/webview/plat_support/graphic_buffer_impl.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+// Provides the implementation of the GraphicBuffer interface in
+// renderer compostior
+
+#include "graphic_buffer_impl.h"
+
+#include <utils/Errors.h>
+
+namespace android {
+
+GraphicBufferImpl::GraphicBufferImpl(uint32_t w, uint32_t h)
+ : mBuffer(new android::GraphicBuffer(w, h, PIXEL_FORMAT_RGBA_8888,
+ android::GraphicBuffer::USAGE_HW_TEXTURE |
+ android::GraphicBuffer::USAGE_SW_READ_OFTEN |
+ android::GraphicBuffer::USAGE_SW_WRITE_OFTEN)) {
+}
+
+GraphicBufferImpl::~GraphicBufferImpl() {
+}
+
+// static
+long GraphicBufferImpl::Create(int w, int h) {
+ GraphicBufferImpl* buffer = new GraphicBufferImpl(
+ static_cast<uint32_t>(w), static_cast<uint32_t>(h));
+ if (buffer->InitCheck() != NO_ERROR) {
+ delete buffer;
+ return 0;
+ }
+ return reinterpret_cast<intptr_t>(buffer);
+}
+
+// static
+void GraphicBufferImpl::Release(long buffer_id) {
+ GraphicBufferImpl* buffer = reinterpret_cast<GraphicBufferImpl*>(buffer_id);
+ delete buffer;
+}
+
+// static
+int GraphicBufferImpl::MapStatic(long buffer_id, AwMapMode mode, void** vaddr) {
+ GraphicBufferImpl* buffer = reinterpret_cast<GraphicBufferImpl*>(buffer_id);
+ return buffer->Map(mode, vaddr);
+}
+
+// static
+int GraphicBufferImpl::UnmapStatic(long buffer_id) {
+ GraphicBufferImpl* buffer = reinterpret_cast<GraphicBufferImpl*>(buffer_id);
+ return buffer->Unmap();
+}
+
+// static
+void* GraphicBufferImpl::GetNativeBufferStatic(long buffer_id) {
+ GraphicBufferImpl* buffer = reinterpret_cast<GraphicBufferImpl*>(buffer_id);
+ return buffer->GetNativeBuffer();
+}
+
+// static
+uint32_t GraphicBufferImpl::GetStrideStatic(long buffer_id) {
+ GraphicBufferImpl* buffer = reinterpret_cast<GraphicBufferImpl*>(buffer_id);
+ return buffer->GetStride();
+}
+
+status_t GraphicBufferImpl::Map(AwMapMode mode, void** vaddr) {
+ int usage = 0;
+ switch (mode) {
+ case MAP_READ_ONLY:
+ usage = android::GraphicBuffer::USAGE_SW_READ_OFTEN;
+ break;
+ case MAP_WRITE_ONLY:
+ usage = android::GraphicBuffer::USAGE_SW_WRITE_OFTEN;
+ break;
+ case MAP_READ_WRITE:
+ usage = android::GraphicBuffer::USAGE_SW_READ_OFTEN |
+ android::GraphicBuffer::USAGE_SW_WRITE_OFTEN;
+ break;
+ default:
+ return INVALID_OPERATION;
+ }
+ return mBuffer->lock(usage, vaddr);
+}
+
+status_t GraphicBufferImpl::Unmap() {
+ return mBuffer->unlock();
+}
+
+status_t GraphicBufferImpl::InitCheck() const {
+ return mBuffer->initCheck();
+}
+
+void* GraphicBufferImpl::GetNativeBuffer() const {
+ return mBuffer->getNativeBuffer();
+}
+
+uint32_t GraphicBufferImpl::GetStride() const {
+ static const int kBytesPerPixel = 4;
+ return mBuffer->getStride() * kBytesPerPixel;
+}
+
+} // namespace android
diff --git a/native/webview/plat_support/graphic_buffer_impl.h b/native/webview/plat_support/graphic_buffer_impl.h
new file mode 100644
index 0000000..442710a
--- /dev/null
+++ b/native/webview/plat_support/graphic_buffer_impl.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+// Provides the implementation of the GraphicBuffer interface in
+// renderer compostior
+
+#ifndef ANDROID_GRAPHIC_BUFFER_IMPL_H
+#define ANDROID_GRAPHIC_BUFFER_IMPL_H
+
+#include <ui/GraphicBuffer.h>
+
+#include "draw_gl.h"
+
+namespace android {
+
+class GraphicBufferImpl {
+ public:
+ ~GraphicBufferImpl();
+
+ static long Create(int w, int h);
+ static void Release(long buffer_id);
+ static int MapStatic(long buffer_id, AwMapMode mode, void** vaddr);
+ static int UnmapStatic(long buffer_id);
+ static void* GetNativeBufferStatic(long buffer_id);
+ static uint32_t GetStrideStatic(long buffer_id);
+
+ private:
+ status_t Map(AwMapMode mode, void** vaddr);
+ status_t Unmap();
+ status_t InitCheck() const;
+ void* GetNativeBuffer() const;
+ uint32_t GetStride() const;
+ GraphicBufferImpl(uint32_t w, uint32_t h);
+
+ sp<android::GraphicBuffer> mBuffer;
+};
+
+} // namespace android
+
+#endif // ANDROID_GRAPHIC_BUFFER_IMPL_H
diff --git a/native/webview/plat_support/graphics_utils.cpp b/native/webview/plat_support/graphics_utils.cpp
new file mode 100644
index 0000000..89beb75
--- /dev/null
+++ b/native/webview/plat_support/graphics_utils.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+// Provides a webviewchromium glue layer adapter from the internal Android
+// graphics types into the types the chromium stack expects, and back.
+
+#define LOG_TAG "webviewchromium_plat_support"
+
+#include "draw_gl.h"
+#include "draw_sw.h"
+
+#include <cstdlib>
+#include <jni.h>
+#include <utils/Log.h>
+#include "graphic_buffer_impl.h"
+#include "GraphicsJNI.h"
+#include "SkCanvasStateUtils.h"
+#include "SkGraphics.h"
+#include "SkPicture.h"
+
+#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
+
+namespace android {
+namespace {
+
+class PixelInfo : public AwPixelInfo {
+ public:
+ explicit PixelInfo(android::Canvas* canvas);
+ ~PixelInfo();
+};
+
+
+PixelInfo::PixelInfo(android::Canvas* canvas) {
+ memset(this, 0, sizeof(AwPixelInfo));
+ version = kAwPixelInfoVersion;
+ state = canvas->captureCanvasState();
+}
+
+PixelInfo::~PixelInfo() {
+ if (state)
+ SkCanvasStateUtils::ReleaseCanvasState(state);
+}
+
+AwPixelInfo* GetPixels(JNIEnv* env, jobject java_canvas) {
+ android::Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, java_canvas);
+ if (!nativeCanvas)
+ return NULL;
+
+ PixelInfo* pixels = new PixelInfo(nativeCanvas);
+ if (!pixels->state) {
+ delete pixels;
+ pixels = NULL;
+ }
+ return pixels;
+}
+
+void ReleasePixels(AwPixelInfo* pixels) {
+ delete static_cast<PixelInfo*>(pixels);
+}
+
+jlong GetDrawSWFunctionTable(JNIEnv* env, jclass) {
+ static AwDrawSWFunctionTable function_table;
+ function_table.version = kAwDrawSWFunctionTableVersion;
+ function_table.access_pixels = &GetPixels;
+ function_table.release_pixels = &ReleasePixels;
+ return reinterpret_cast<intptr_t>(&function_table);
+}
+
+jlong GetDrawGLFunctionTable(JNIEnv* env, jclass) {
+ static AwDrawGLFunctionTable function_table;
+ function_table.version = kAwDrawGLFunctionTableVersion;
+ function_table.create_graphic_buffer = &GraphicBufferImpl::Create;
+ function_table.release_graphic_buffer = &GraphicBufferImpl::Release;
+ function_table.map = &GraphicBufferImpl::MapStatic;
+ function_table.unmap = &GraphicBufferImpl::UnmapStatic;
+ function_table.get_native_buffer = &GraphicBufferImpl::GetNativeBufferStatic;
+ function_table.get_stride = &GraphicBufferImpl::GetStrideStatic;
+ return reinterpret_cast<intptr_t>(&function_table);
+}
+
+const char kClassName[] = "com/android/webview/chromium/GraphicsUtils";
+const JNINativeMethod kJniMethods[] = {
+ { "nativeGetDrawSWFunctionTable", "()J",
+ reinterpret_cast<void*>(GetDrawSWFunctionTable) },
+ { "nativeGetDrawGLFunctionTable", "()J",
+ reinterpret_cast<void*>(GetDrawGLFunctionTable) },
+};
+
+} // namespace
+
+void RegisterGraphicsUtils(JNIEnv* env) {
+ jclass clazz = env->FindClass(kClassName);
+ LOG_ALWAYS_FATAL_IF(!clazz, "Unable to find class '%s'", kClassName);
+
+ int res = env->RegisterNatives(clazz, kJniMethods, NELEM(kJniMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "register native methods failed: res=%d", res);
+}
+
+} // namespace android
diff --git a/native/webview/plat_support/jni_entry_point.cpp b/native/webview/plat_support/jni_entry_point.cpp
new file mode 100644
index 0000000..4771be1
--- /dev/null
+++ b/native/webview/plat_support/jni_entry_point.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#define LOG_TAG "webviewchromium_plat_support"
+
+#include <jni.h>
+#include <utils/Log.h>
+
+namespace android {
+
+void RegisterDrawGLFunctor(JNIEnv* env);
+void RegisterGraphicsUtils(JNIEnv* env);
+
+} // namespace android
+
+JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ JNIEnv* env = NULL;
+ jint ret = vm->AttachCurrentThread(&env, NULL);
+ LOG_ALWAYS_FATAL_IF(ret != JNI_OK, "AttachCurrentThread failed");
+ android::RegisterDrawGLFunctor(env);
+ android::RegisterGraphicsUtils(env);
+
+ return JNI_VERSION_1_4;
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index ae544dd..df1a7a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -37,7 +37,6 @@
import android.net.wifi.WifiNetworkScoreCache.CacheListener;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
@@ -104,7 +103,6 @@
// TODO: Allow control of this?
// Combo scans can take 5-6s to complete - set to 10s.
private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
- private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
private final Context mContext;
private final WifiManager mWifiManager;
@@ -112,30 +110,38 @@
private final ConnectivityManager mConnectivityManager;
private final NetworkRequest mNetworkRequest;
private final AtomicBoolean mConnected = new AtomicBoolean(false);
- private final WifiListener mListener;
- @VisibleForTesting WorkHandler mWorkHandler;
+ private final WifiListenerExecutor mListener;
+ @VisibleForTesting Handler mWorkHandler;
private HandlerThread mWorkThread;
private WifiTrackerNetworkCallback mNetworkCallback;
- @GuardedBy("mLock")
- private boolean mRegistered;
+ /**
+ * Synchronization lock for managing concurrency between main and worker threads.
+ *
+ * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints}.
+ */
+ private final Object mLock = new Object();
/** The list of AccessPoints, aggregated visible ScanResults with metadata. */
@GuardedBy("mLock")
private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
+ @GuardedBy("mLock")
+ private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
+
/**
- * Synchronization lock for managing concurrency between main and worker threads.
- *
- * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints}.
- */
- private final Object mLock = new Object();
+ * Tracks whether fresh scan results have been received since scanning start.
+ *
+ * <p>If this variable is false, we will not evict the scan result cache or invoke callbacks
+ * so that we do not update the UI with stale data / clear out existing UI elements prematurely.
+ */
+ private boolean mStaleScanResults = true;
- private final HashMap<String, Integer> mSeenBssids = new HashMap<>();
-
- // TODO(sghuman): Change this to be keyed on AccessPoint.getKey
+ // Does not need to be locked as it only updated on the worker thread, with the exception of
+ // during onStart, which occurs before the receiver is registered on the work handler.
private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
+ private boolean mRegistered;
private NetworkInfo mLastNetworkInfo;
private WifiInfo mLastInfo;
@@ -145,21 +151,11 @@
private boolean mNetworkScoringUiEnabled;
private long mMaxSpeedLabelScoreCacheAge;
- @GuardedBy("mLock")
- private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
+
@VisibleForTesting
Scanner mScanner;
- /**
- * Tracks whether fresh scan results have been received since scanning start.
- *
- * <p>If this variable is false, we will not evict the scan result cache or invoke callbacks
- * so that we do not update the UI with stale data / clear out existing UI elements prematurely.
- */
- @GuardedBy("mLock")
- private boolean mStaleScanResults = true;
-
private static IntentFilter newIntentFilter() {
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
@@ -180,7 +176,7 @@
@Deprecated
public WifiTracker(Context context, WifiListener wifiListener,
boolean includeSaved, boolean includeScans) {
- this(context, wifiListener,
+ this(context, new WifiListenerExecutor(wifiListener),
context.getSystemService(WifiManager.class),
context.getSystemService(ConnectivityManager.class),
context.getSystemService(NetworkScoreManager.class),
@@ -191,7 +187,7 @@
// calling apps once IC window is complete
public WifiTracker(Context context, WifiListener wifiListener,
@NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
- this(context, wifiListener,
+ this(context, new WifiListenerExecutor(wifiListener),
context.getSystemService(WifiManager.class),
context.getSystemService(ConnectivityManager.class),
context.getSystemService(NetworkScoreManager.class),
@@ -200,13 +196,13 @@
}
@VisibleForTesting
- WifiTracker(Context context, WifiListener wifiListener,
+ WifiTracker(Context context, WifiListenerExecutor wifiListenerExecutor,
WifiManager wifiManager, ConnectivityManager connectivityManager,
NetworkScoreManager networkScoreManager,
IntentFilter filter) {
mContext = context;
mWifiManager = wifiManager;
- mListener = new WifiListenerWrapper(wifiListener);
+ mListener = wifiListenerExecutor;
mConnectivityManager = connectivityManager;
// check if verbose logging developer option has been turned on or off
@@ -238,13 +234,11 @@
// during construction
void setWorkThread(HandlerThread workThread) {
mWorkThread = workThread;
- mWorkHandler = new WorkHandler(workThread.getLooper());
+ mWorkHandler = new Handler(workThread.getLooper());
mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
@Override
public void networkCacheUpdated(List<ScoredNetwork> networks) {
- synchronized (mLock) {
- if (!mRegistered) return;
- }
+ if (!mRegistered) return;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Score cache was updated with networks: " + networks);
@@ -259,25 +253,6 @@
mWorkThread.quit();
}
- /** Synchronously update the list of access points with the latest information. */
- @MainThread
- public void forceUpdate() {
- synchronized (mLock) {
- mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
- mLastInfo = mWifiManager.getConnectionInfo();
- mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
-
- final List<ScanResult> newScanResults = mWifiManager.getScanResults();
- if (isVerboseLoggingEnabled()) {
- Log.i(TAG, "Fetched scan results: " + newScanResults);
- }
-
- List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
- mInternalAccessPoints.clear();
- updateAccessPointsLocked(newScanResults, configs);
- }
- }
-
/**
* Temporarily stop scanning for wifi networks.
*
@@ -288,9 +263,7 @@
mScanner.pause();
mScanner = null;
}
- synchronized (mLock) {
- mStaleScanResults = true;
- }
+ mStaleScanResults = true;
}
/**
@@ -303,7 +276,6 @@
mScanner = new Scanner();
}
- mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
if (mWifiManager.isWifiEnabled()) {
mScanner.resume();
}
@@ -318,31 +290,46 @@
@Override
@MainThread
public void onStart() {
- synchronized (mLock) {
- registerScoreCache();
+ // fetch current ScanResults instead of waiting for broadcast of fresh results
+ forceUpdate();
- mNetworkScoringUiEnabled =
- Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
+ registerScoreCache();
- mMaxSpeedLabelScoreCacheAge =
- Settings.Global.getLong(
- mContext.getContentResolver(),
- Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
- DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS);
+ mNetworkScoringUiEnabled =
+ Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
- resumeScanning();
- if (!mRegistered) {
- mContext.registerReceiver(mReceiver, mFilter);
- // NetworkCallback objects cannot be reused. http://b/20701525 .
- mNetworkCallback = new WifiTrackerNetworkCallback();
- mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
- mRegistered = true;
- }
+ mMaxSpeedLabelScoreCacheAge =
+ Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
+ DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS);
+
+ resumeScanning();
+ if (!mRegistered) {
+ mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler);
+ // NetworkCallback objects cannot be reused. http://b/20701525 .
+ mNetworkCallback = new WifiTrackerNetworkCallback();
+ mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
+ mRegistered = true;
}
}
+
+ /**
+ * Synchronously update the list of access points with the latest information.
+ *
+ * <p>Intended to only be invoked within {@link #onStart()}.
+ */
+ @MainThread
+ private void forceUpdate() {
+ mLastInfo = mWifiManager.getConnectionInfo();
+ mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
+
+ fetchScansAndConfigsAndUpdateAccessPoints();
+ }
+
private void registerScoreCache() {
mNetworkScoreManager.registerNetworkScoreCache(
NetworkKey.TYPE_WIFI,
@@ -375,17 +362,15 @@
@Override
@MainThread
public void onStop() {
- synchronized (mLock) {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
- mRegistered = false;
- }
- unregisterScoreCache();
- pauseScanning(); // and set mStaleScanResults
-
- mWorkHandler.removePendingMessages();
+ if (mRegistered) {
+ mContext.unregisterReceiver(mReceiver);
+ mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+ mRegistered = false;
}
+ unregisterScoreCache();
+ pauseScanning(); // and set mStaleScanResults
+
+ mWorkHandler.removeCallbacksAndMessages(null /* remove all */);
}
private void unregisterScoreCache() {
@@ -409,9 +394,6 @@
*/
@AnyThread
public List<AccessPoint> getAccessPoints() {
- // TODO(sghuman): Investigate how to eliminate or reduce the need for locking now that we
- // have transitioned to a single worker thread model.
-
synchronized (mLock) {
return new ArrayList<>(mInternalAccessPoints);
}
@@ -446,15 +428,10 @@
}
}
- private void handleResume() {
- // TODO(sghuman): Investigate removing this and replacing it with a cache eviction call
- // instead.
- mScanResultCache.clear();
- mSeenBssids.clear();
- }
-
- private Collection<ScanResult> updateScanResultCache(final List<ScanResult> newResults) {
- // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults
+ private ArrayMap<String, List<ScanResult>> updateScanResultCache(
+ final List<ScanResult> newResults) {
+ // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults for
+ // memory efficiency
for (ScanResult newResult : newResults) {
if (newResult.SSID == null || newResult.SSID.isEmpty()) {
continue;
@@ -467,10 +444,27 @@
evictOldScans();
}
- // TODO(sghuman): Update a Map<ApKey, List<ScanResults>> variable to be reused later after
- // double threads have been removed.
+ ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>();
+ for (ScanResult result : mScanResultCache.values()) {
+ // Ignore hidden and ad-hoc networks.
+ if (result.SSID == null || result.SSID.length() == 0 ||
+ result.capabilities.contains("[IBSS]")) {
+ continue;
+ }
- return mScanResultCache.values();
+ String apKey = AccessPoint.getKey(result);
+ List<ScanResult> resultList;
+ if (scanResultsByApKey.containsKey(apKey)) {
+ resultList = scanResultsByApKey.get(apKey);
+ } else {
+ resultList = new ArrayList<>();
+ scanResultsByApKey.put(apKey, resultList);
+ }
+
+ resultList.add(result);
+ }
+
+ return scanResultsByApKey;
}
/**
@@ -504,93 +498,59 @@
}
/**
- * Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first.
- *
- * <p>Will not perform the update if {@link #mStaleScanResults} is true
+ * Retrieves latest scan results and wifi configs, then calls
+ * {@link #updateAccessPoints(List, List)}.
*/
- private void updateAccessPoints() {
- List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
+ private void fetchScansAndConfigsAndUpdateAccessPoints() {
final List<ScanResult> newScanResults = mWifiManager.getScanResults();
if (isVerboseLoggingEnabled()) {
Log.i(TAG, "Fetched scan results: " + newScanResults);
}
- synchronized (mLock) {
- if(!mStaleScanResults) {
- updateAccessPointsLocked(newScanResults, configs);
- }
- }
+ List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
+ updateAccessPoints(newScanResults, configs);
}
- /**
- * Update the internal list of access points.
- *
- * <p>Do not call directly (except for forceUpdate), use {@link #updateAccessPoints()} which
- * acquires the lock first.
- */
- @GuardedBy("mLock")
- private void updateAccessPointsLocked(final List<ScanResult> newScanResults,
+ /** Update the internal list of access points. */
+ private void updateAccessPoints(final List<ScanResult> newScanResults,
List<WifiConfiguration> configs) {
- // TODO(sghuman): Reduce the synchronization time by only holding the lock when
- // modifying lists exposed to operations on the MainThread (getAccessPoints, stopTracking,
- // startTracking, etc).
- WifiConfiguration connectionConfig = null;
- if (mLastInfo != null) {
- connectionConfig = getWifiConfigurationForNetworkId(
- mLastInfo.getNetworkId(), configs);
- }
-
- // Swap the current access points into a cached list.
- List<AccessPoint> cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
- ArrayList<AccessPoint> accessPoints = new ArrayList<>();
-
- // Clear out the configs so we don't think something is saved when it isn't.
- for (AccessPoint accessPoint : cachedAccessPoints) {
- accessPoint.clearConfig();
- }
-
- final Collection<ScanResult> results = updateScanResultCache(newScanResults);
-
+ // Map configs and scan results necessary to make AccessPoints
final Map<String, WifiConfiguration> configsByKey = new ArrayMap(configs.size());
if (configs != null) {
for (WifiConfiguration config : configs) {
configsByKey.put(AccessPoint.getKey(config), config);
}
}
+ ArrayMap<String, List<ScanResult>> scanResultsByApKey =
+ updateScanResultCache(newScanResults);
- final List<NetworkKey> scoresToRequest = new ArrayList<>();
- if (results != null) {
- // TODO(sghuman): Move this loop to updateScanResultCache and make instance variable
- // after double handlers are removed.
- ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>();
- for (ScanResult result : results) {
- // Ignore hidden and ad-hoc networks.
- if (result.SSID == null || result.SSID.length() == 0 ||
- result.capabilities.contains("[IBSS]")) {
- continue;
- }
+ WifiConfiguration connectionConfig = null;
+ if (mLastInfo != null) {
+ // TODO(sghuman): Refactor to match config network id when updating configs below, and
+ // then update network info and wifi info only on match
+ connectionConfig = getWifiConfigurationForNetworkId(
+ mLastInfo.getNetworkId(), configs);
+ }
- NetworkKey key = NetworkKey.createFromScanResult(result);
- if (key != null && !mRequestedScores.contains(key)) {
- scoresToRequest.add(key);
- }
+ // Rather than dropping and reacquiring the lock multiple times in this method, we lock
+ // once for efficiency of lock acquisition time and readability
+ synchronized (mLock) {
+ // Swap the current access points into a cached list for maintaining AP listeners
+ List<AccessPoint> cachedAccessPoints;
+ cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
- String apKey = AccessPoint.getKey(result);
- List<ScanResult> resultList;
- if (scanResultsByApKey.containsKey(apKey)) {
- resultList = scanResultsByApKey.get(apKey);
- } else {
- resultList = new ArrayList<>();
- scanResultsByApKey.put(apKey, resultList);
- }
+ ArrayList<AccessPoint> accessPoints = new ArrayList<>();
- resultList.add(result);
- }
+ final List<NetworkKey> scoresToRequest = new ArrayList<>();
for (Map.Entry<String, List<ScanResult>> entry : scanResultsByApKey.entrySet()) {
- // List can not be empty as it is dynamically constructed on each iteration
- ScanResult firstResult = entry.getValue().get(0);
+ for (ScanResult result : entry.getValue()) {
+ NetworkKey key = NetworkKey.createFromScanResult(result);
+ if (key != null && !mRequestedScores.contains(key)) {
+ scoresToRequest.add(key);
+ }
+ }
AccessPoint accessPoint =
getCachedOrCreate(entry.getValue(), cachedAccessPoints);
@@ -599,46 +559,43 @@
}
// Update the matching config if there is one, to populate saved network info
- WifiConfiguration config = configsByKey.get(entry.getKey());
- if (config != null) {
- accessPoint.update(config);
- }
+ accessPoint.update(configsByKey.get(entry.getKey()));
accessPoints.add(accessPoint);
}
- }
- requestScoresForNetworkKeys(scoresToRequest);
- for (AccessPoint ap : accessPoints) {
- ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge);
- }
-
- // Pre-sort accessPoints to speed preference insertion
- Collections.sort(accessPoints);
-
- // Log accesspoints that were deleted
- if (DBG()) {
- Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
- for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
- if (prevAccessPoint.getSsid() == null)
- continue;
- String prevSsid = prevAccessPoint.getSsidStr();
- boolean found = false;
- for (AccessPoint newAccessPoint : accessPoints) {
- if (newAccessPoint.getSsidStr() != null && newAccessPoint.getSsidStr()
- .equals(prevSsid)) {
- found = true;
- break;
- }
- }
- if (!found)
- Log.d(TAG, "Did not find " + prevSsid + " in this scan");
+ requestScoresForNetworkKeys(scoresToRequest);
+ for (AccessPoint ap : accessPoints) {
+ ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge);
}
- Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
- }
- mInternalAccessPoints.clear();
- mInternalAccessPoints.addAll(accessPoints);
+ // Pre-sort accessPoints to speed preference insertion
+ Collections.sort(accessPoints);
+
+ // Log accesspoints that are being removed
+ if (DBG()) {
+ Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
+ for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
+ if (prevAccessPoint.getSsid() == null)
+ continue;
+ String prevSsid = prevAccessPoint.getSsidStr();
+ boolean found = false;
+ for (AccessPoint newAccessPoint : accessPoints) {
+ if (newAccessPoint.getSsidStr() != null && newAccessPoint.getSsidStr()
+ .equals(prevSsid)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ Log.d(TAG, "Did not find " + prevSsid + " in this scan");
+ }
+ Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
+ }
+
+ mInternalAccessPoints.clear();
+ mInternalAccessPoints.addAll(accessPoints);
+ }
conditionallyNotifyListeners();
}
@@ -659,21 +616,6 @@
return accessPoint;
}
- @VisibleForTesting
- AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
- final int N = cache.size();
- for (int i = 0; i < N; i++) {
- if (cache.get(i).matches(config)) {
- AccessPoint ret = cache.remove(i);
- ret.loadConfig(config);
-
- return ret;
- }
- }
- final AccessPoint accessPoint = new AccessPoint(mContext, config);
- return accessPoint;
- }
-
private void updateNetworkInfo(NetworkInfo networkInfo) {
/* Sticky broadcasts can call this when wifi is disabled */
@@ -739,7 +681,7 @@
synchronized (mLock) {
if (!mInternalAccessPoints.isEmpty()) {
mInternalAccessPoints.clear();
- mListener.onAccessPointsChanged();
+ conditionallyNotifyListeners();
}
}
}
@@ -767,126 +709,82 @@
}
}
+ /**
+ * Receiver for handling broadcasts.
+ *
+ * This receiver is registered on the WorkHandler.
+ */
@VisibleForTesting
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- // No work should be performed in this Receiver, instead all operations should be passed
- // off to the WorkHandler to avoid concurrent modification exceptions.
-
String action = intent.getAction();
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
- mWorkHandler.obtainMessage(
- WorkHandler.MSG_UPDATE_WIFI_STATE,
+ updateWifiState(
intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
- WifiManager.WIFI_STATE_UNKNOWN),
- 0).sendToTarget();
+ WifiManager.WIFI_STATE_UNKNOWN));
} else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
- mWorkHandler
- .obtainMessage(
- WorkHandler.MSG_UPDATE_ACCESS_POINTS,
- WorkHandler.CLEAR_STALE_SCAN_RESULTS,
- 0)
- .sendToTarget();
+ mStaleScanResults = false;
+
+ fetchScansAndConfigsAndUpdateAccessPoints();
} else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
|| WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
- mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
+ fetchScansAndConfigsAndUpdateAccessPoints();
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+ // TODO(sghuman): Refactor these methods so they cannot result in duplicate
+ // onAccessPointsChanged updates being called from this intent.
NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
- mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
- .sendToTarget();
- mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
+ updateNetworkInfo(info);
+ fetchScansAndConfigsAndUpdateAccessPoints();
} else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
NetworkInfo info =
mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
- mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
- .sendToTarget();
+ updateNetworkInfo(info);
}
}
};
+ /**
+ * Handles updates to WifiState.
+ *
+ * <p>If Wifi is not enabled in the enabled state, {@link #mStaleScanResults} will be set to
+ * true.
+ */
+ private void updateWifiState(int state) {
+ if (state == WifiManager.WIFI_STATE_ENABLED) {
+ if (mScanner != null) {
+ // We only need to resume if mScanner isn't null because
+ // that means we want to be scanning.
+ mScanner.resume();
+ }
+ } else {
+ clearAccessPointsAndConditionallyUpdate();
+ mLastInfo = null;
+ mLastNetworkInfo = null;
+ if (mScanner != null) {
+ mScanner.pause();
+ }
+ mStaleScanResults = true;
+ }
+ mListener.onWifiStateChanged(state);
+ }
+
private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
if (network.equals(mWifiManager.getCurrentNetwork())) {
+ // TODO(sghuman): Investigate whether this comment still holds true and if it makes
+ // more sense fetch the latest network info here:
+
// We don't send a NetworkInfo object along with this message, because even if we
// fetch one from ConnectivityManager, it might be older than the most recent
// NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
- mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
+ mWorkHandler.post(() -> updateNetworkInfo(null));
}
}
}
@VisibleForTesting
- final class WorkHandler extends Handler {
- @VisibleForTesting static final int MSG_UPDATE_ACCESS_POINTS = 0;
- private static final int MSG_UPDATE_NETWORK_INFO = 1;
- private static final int MSG_RESUME = 2;
- private static final int MSG_UPDATE_WIFI_STATE = 3;
-
- private static final int CLEAR_STALE_SCAN_RESULTS = 1;
-
- public WorkHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- // TODO(sghuman): Clean up synchronization to only be used when modifying collections
- // exposed to the MainThread (through onStart, onStop, forceUpdate).
- synchronized (mLock) {
- processMessage(msg);
- }
- }
-
- private void processMessage(Message msg) {
- if (!mRegistered) return;
-
- switch (msg.what) {
- case MSG_UPDATE_ACCESS_POINTS:
- if (msg.arg1 == CLEAR_STALE_SCAN_RESULTS) {
- mStaleScanResults = false;
- }
- updateAccessPoints();
- break;
- case MSG_UPDATE_NETWORK_INFO:
- updateNetworkInfo((NetworkInfo) msg.obj);
- break;
- case MSG_RESUME:
- handleResume();
- break;
- case MSG_UPDATE_WIFI_STATE:
- if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) {
- if (mScanner != null) {
- // We only need to resume if mScanner isn't null because
- // that means we want to be scanning.
- mScanner.resume();
- }
- } else {
- clearAccessPointsAndConditionallyUpdate();
- mLastInfo = null;
- mLastNetworkInfo = null;
- if (mScanner != null) {
- mScanner.pause();
- }
- synchronized (mLock) {
- mStaleScanResults = true;
- }
- }
- mListener.onWifiStateChanged(msg.arg1);
- break;
- }
- }
-
- private void removePendingMessages() {
- removeMessages(MSG_UPDATE_ACCESS_POINTS);
- removeMessages(MSG_UPDATE_NETWORK_INFO);
- removeMessages(MSG_RESUME);
- removeMessages(MSG_UPDATE_WIFI_STATE);
- }
- }
-
- @VisibleForTesting
class Scanner extends Handler {
static final int MSG_SCAN = 0;
@@ -944,19 +842,16 @@
}
/**
- * Wraps the given {@link WifiListener} instance and executes it's methods on the Main Thread.
+ * Wraps the given {@link WifiListener} instance and executes its methods on the Main Thread.
*
- * <p>This mechanism allows us to no longer need a separate MainHandler and WorkHandler, which
- * were previously both performing work, while avoiding errors which occur from executing
- * callbacks which manipulate UI elements from a different thread than the MainThread.
+ * <p>Also logs all callbacks invocations when verbose logging is enabled.
*/
- private static class WifiListenerWrapper implements WifiListener {
+ @VisibleForTesting
+ public static class WifiListenerExecutor implements WifiListener {
- private final Handler mHandler;
private final WifiListener mDelegatee;
- public WifiListenerWrapper(WifiListener listener) {
- mHandler = new Handler(Looper.getMainLooper());
+ public WifiListenerExecutor(WifiListener listener) {
mDelegatee = listener;
}
@@ -966,7 +861,7 @@
Log.i(TAG,
String.format("Invoking onWifiStateChanged callback with state %d", state));
}
- mHandler.post(() -> mDelegatee.onWifiStateChanged(state));
+ ThreadUtils.postOnMainThread(() -> mDelegatee.onWifiStateChanged(state));
}
@Override
@@ -974,7 +869,7 @@
if (isVerboseLoggingEnabled()) {
Log.i(TAG, "Invoking onConnectedChanged callback");
}
- mHandler.post(() -> mDelegatee.onConnectedChanged());
+ ThreadUtils.postOnMainThread(() -> mDelegatee.onConnectedChanged());
}
@Override
@@ -982,10 +877,15 @@
if (isVerboseLoggingEnabled()) {
Log.i(TAG, "Invoking onAccessPointsChanged callback");
}
- mHandler.post(() -> mDelegatee.onAccessPointsChanged());
+ ThreadUtils.postOnMainThread(() -> mDelegatee.onAccessPointsChanged());
}
}
+ /**
+ * WifiListener interface that defines callbacks indicating state changes in WifiTracker.
+ *
+ * <p>All callbacks are invoked on the MainThread.
+ */
public interface WifiListener {
/**
* Called when the state of Wifi has changed, the state will be one of
@@ -1016,7 +916,7 @@
}
/**
- * Invokes {@link WifiListenerWrapper#onAccessPointsChanged()} if {@link #mStaleScanResults}
+ * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults}
* is false.
*/
private void conditionallyNotifyListeners() {
@@ -1024,6 +924,6 @@
return;
}
- ThreadUtils.postOnMainThread(() -> mListener.onAccessPointsChanged());
+ mListener.onAccessPointsChanged();
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 0c49bb6..8fd4700 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -21,18 +21,15 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -58,7 +55,6 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -76,12 +72,10 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.BitSet;
-import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
// TODO(sghuman): Change these to robolectric tests b/35766684.
@@ -141,7 +135,7 @@
@Mock private RssiCurve mockBadgeCurve1;
@Mock private RssiCurve mockBadgeCurve2;
@Mock private WifiManager mockWifiManager;
- @Mock private WifiTracker.WifiListener mockWifiListener;
+ @Mock private WifiTracker.WifiListenerExecutor mockWifiListenerExecutor;
private final List<NetworkKey> mRequestedKeys = new ArrayList<>();
@@ -153,6 +147,7 @@
private int mOriginalScoringUiSettingValue;
+ @SuppressWarnings("VisibleForTests")
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -203,17 +198,14 @@
}
}).when(mockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any());
- doAnswer(
- new Answer<Void>() {
- @Override
- public Void answer (InvocationOnMock invocation) throws Throwable {
+ // We use a latch to detect callbacks as Tracker initialization state often invokes
+ // callbacks
+ doAnswer(invocation -> {
if (mAccessPointsChangedLatch != null) {
mAccessPointsChangedLatch.countDown();
}
-
return null;
- }
- }).when(mockWifiListener).onAccessPointsChanged();
+ }).when(mockWifiListenerExecutor).onAccessPointsChanged();
// Turn on Scoring UI features
mOriginalScoringUiSettingValue = Settings.Global.getInt(
@@ -271,8 +263,7 @@
tracker.mReceiver.onReceive(mContext, intent);
}
- sendScanResultsAndProcess(tracker);
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
+ sendScanResults(tracker);
return tracker;
}
@@ -280,7 +271,7 @@
private WifiTracker createMockedWifiTracker() {
final WifiTracker wifiTracker = new WifiTracker(
mContext,
- mockWifiListener,
+ mockWifiListenerExecutor,
mockWifiManager,
mockConnectivityManager,
mockNetworkScoreManager,
@@ -291,26 +282,19 @@
private void startTracking(WifiTracker tracker) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
- mScannerHandler.post(new Runnable() {
- @Override
- public void run() {
+ mScannerHandler.post(() -> {
tracker.onStart();
latch.countDown();
- }
});
assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
}
- private void sendScanResultsAndProcess(WifiTracker tracker) throws InterruptedException {
- mAccessPointsChangedLatch = new CountDownLatch(1);
+ private void sendScanResults(WifiTracker tracker) throws InterruptedException {
Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
tracker.mReceiver.onReceive(mContext, i);
-
- assertTrue("Latch timed out",
- mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
}
- private void updateScores() {
+ private void sendUpdatedScores() throws InterruptedException {
Bundle attr1 = new Bundle();
attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve1);
ScoredNetwork sc1 =
@@ -356,12 +340,8 @@
private void waitForHandlersToProcessCurrentlyEnqueuedMessages(WifiTracker tracker)
throws InterruptedException {
- // TODO(sghuman): This should no longer be necessary in a single work handler model
-
CountDownLatch workerLatch = new CountDownLatch(1);
- tracker.mWorkHandler.post(() -> {
- workerLatch.countDown();
- });
+ tracker.mWorkHandler.post(() -> workerLatch.countDown());
assertTrue("Latch timed out while waiting for WorkerHandler",
workerLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
}
@@ -381,7 +361,6 @@
Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
tracker.mReceiver.onReceive(mContext, intent);
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
}
@Test
@@ -457,7 +436,6 @@
mRequestScoresLatch = new CountDownLatch(1);
startTracking(tracker);
- tracker.forceUpdate();
assertTrue("Latch timed out",
mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
@@ -470,7 +448,7 @@
throws InterruptedException {
// Start the tracker and inject the initial scan results and then stop tracking
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
- updateScoresAndWaitForAccessPointsChangedCallback(tracker);
+ updateScoresAndWaitForCacheListenerToProcess(tracker);
tracker.onStop();
assertThat(mScoreCacheCaptor.getValue().getScoredNetwork(NETWORK_KEY_1)).isNotNull();
@@ -481,19 +459,18 @@
throws InterruptedException {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
- sendScanResultsAndProcess(tracker);
+ sendScanResults(tracker);
- updateScoresAndWaitForAccessPointsChangedCallback(tracker);
+ updateScoresAndWaitForCacheListenerToProcess(tracker);
}
- private void updateScoresAndWaitForAccessPointsChangedCallback(WifiTracker tracker)
+ private void updateScoresAndWaitForCacheListenerToProcess(WifiTracker tracker)
throws InterruptedException {
- // Updating scores can happen together or one after the other, so the latch countdown is set
- // to 2.
- mAccessPointsChangedLatch = new CountDownLatch(1);
- updateScores();
- assertTrue("onAccessPointChanged was not called after updating scores",
- mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+ // Scores are updated via the cache listener hence we need to wait for the work handler
+ // to finish before proceeding.
+ sendUpdatedScores();
+
+ // Ensure the work handler has processed the scores inside the cache listener of WifiTracker
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
}
@@ -505,7 +482,7 @@
assertEquals(aps.get(0).getSsidStr(), SSID_1);
assertEquals(aps.get(1).getSsidStr(), SSID_2);
- updateScoresAndWaitForAccessPointsChangedCallback(tracker);
+ updateScoresAndWaitForCacheListenerToProcess(tracker);
aps = tracker.getAccessPoints();
assertTrue(aps.size() == 2);
@@ -527,7 +504,7 @@
assertEquals(aps.get(0).getSsidStr(), SSID_1);
assertEquals(aps.get(1).getSsidStr(), SSID_2);
- updateScoresAndWaitForAccessPointsChangedCallback(tracker);
+ updateScoresAndWaitForCacheListenerToProcess(tracker);
aps = tracker.getAccessPoints();
assertTrue(aps.size() == 2);
@@ -535,12 +512,11 @@
assertEquals(aps.get(1).getSsidStr(), SSID_2);
}
- @FlakyTest
@Test
public void scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint()
throws InterruptedException {
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
- updateScoresAndWaitForAccessPointsChangedCallback(tracker);
+ updateScoresAndWaitForCacheListenerToProcess(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
@@ -557,7 +533,7 @@
public void scoreCacheUpdateMeteredShouldUpdateAccessPointMetering()
throws InterruptedException {
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
- updateScoresAndWaitForAccessPointsChangedCallback(tracker);
+ updateScoresAndWaitForCacheListenerToProcess(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
@@ -579,7 +555,7 @@
0 /* disabled */);
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
- updateScoresAndWaitForAccessPointsChangedCallback(tracker);
+ updateScoresAndWaitForCacheListenerToProcess(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
@@ -617,7 +593,7 @@
.thenReturn(Arrays.asList(buildScanResult1(), buildScanResult2(), newResult));
mRequestScoresLatch = new CountDownLatch(1);
- sendScanResultsAndProcess(tracker);
+ sendScanResults(tracker);
assertTrue(mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
assertEquals(1, mRequestedKeys.size());
@@ -636,7 +612,7 @@
// Verify listener is unregistered so updating a score does not throw an error by posting
// a message to the dead work handler
mWorkerThread.quit();
- updateScores();
+ sendUpdatedScores();
}
/**
@@ -670,7 +646,8 @@
when(mockWifiManager.getConfiguredNetworks())
.thenReturn(new ArrayList<WifiConfiguration>());
when(mockWifiManager.getScanResults()).thenReturn(results);
- tracker.forceUpdate();
+
+ startTracking(tracker);
}
@Test
@@ -682,26 +659,19 @@
WifiInfo info = new WifiInfo(CONNECTED_AP_1_INFO);
info.setRssi(newRssi);
- CountDownLatch latch = new CountDownLatch(1);
-
// Once the new info has been fetched, we need to wait for the access points to be copied
mAccessPointsChangedLatch = new CountDownLatch(1);
- doAnswer(invocation -> {
- latch.countDown();
- return info;
- }).when(mockWifiManager).getConnectionInfo();
+ doAnswer(invocation -> info).when(mockWifiManager).getConnectionInfo();
tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.RSSI_CHANGED_ACTION));
- assertTrue("New connection info never retrieved",
- latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+
assertTrue("onAccessPointsChanged never called",
mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
-
assertThat(tracker.getAccessPoints().get(0).getRssi()).isEqualTo(newRssi);
}
@Test
- public void forceUpdateShouldSynchronouslyFetchLatestInformation() throws Exception {
+ public void onStartShouldSynchronouslyFetchLatestInformation() throws Exception {
Network mockNetwork = mock(Network.class);
when(mockWifiManager.getCurrentNetwork()).thenReturn(mockNetwork);
@@ -713,19 +683,20 @@
when(mockConnectivityManager.getNetworkInfo(any(Network.class))).thenReturn(networkInfo);
WifiTracker tracker = createMockedWifiTracker();
- tracker.forceUpdate();
+ startTracking(tracker);
verify(mockWifiManager).getConnectionInfo();
verify(mockWifiManager, times(1)).getConfiguredNetworks();
verify(mockConnectivityManager).getNetworkInfo(any(Network.class));
- verify(mockWifiListener, never()).onAccessPointsChanged(); // mStaleAccessPoints is true
+ // mStaleAccessPoints is true
+ verify(mockWifiListenerExecutor, never()).onAccessPointsChanged();
assertThat(tracker.getAccessPoints().size()).isEqualTo(2);
assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue();
}
@Test
- public void stopTrackingShouldRemoveWifiListenerCallbacks() throws Exception {
+ public void stopTrackingShouldRemoveAllPendingWork() throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
@@ -743,25 +714,21 @@
});
// Enqueue messages
- tracker.mWorkHandler.sendEmptyMessage(WifiTracker.WorkHandler.MSG_UPDATE_ACCESS_POINTS);
+ final AtomicBoolean executed = new AtomicBoolean(false);
+ tracker.mWorkHandler.post(() -> executed.set(true));
try {
ready.await(); // Make sure we have entered the first message handler
} catch (InterruptedException e) {}
tracker.onStop();
- verify(mockWifiListener, atMost(1)).onAccessPointsChanged();
- verify(mockWifiListener, atMost(1)).onConnectedChanged();
- verify(mockWifiListener, atMost(1)).onWifiStateChanged(anyInt());
-
lock.countDown();
assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
- assertThat(tracker.mWorkHandler.hasMessages(
- WifiTracker.WorkHandler.MSG_UPDATE_ACCESS_POINTS)).isFalse();
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
+ // In case the method was already executing
+ assertThat(tracker.mWorkHandler.hasMessagesOrCallbacks()).isFalse();
- verifyNoMoreInteractions(mockWifiListener);
+ assertThat(executed.get()).isFalse();
}
@Test
@@ -769,10 +736,8 @@
throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
tracker.onStop();
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
startTracking(tracker);
@@ -782,11 +747,10 @@
tracker.mReceiver.onReceive(
mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
- verify(mockWifiListener, never()).onAccessPointsChanged();
+ verify(mockWifiListenerExecutor, never()).onAccessPointsChanged();
- sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked
+ sendScanResults(tracker); // verifies onAccessPointsChanged is invoked
}
@Test
@@ -794,7 +758,6 @@
throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
tracker.mReceiver.onReceive(
@@ -802,10 +765,9 @@
tracker.mReceiver.onReceive(
mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
- verify(mockWifiListener, never()).onAccessPointsChanged();
+ verify(mockWifiListenerExecutor, never()).onAccessPointsChanged();
- sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked
+ sendScanResults(tracker); // verifies onAccessPointsChanged is invoked
}
@Test
@@ -813,11 +775,10 @@
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
when(mockWifiManager.isWifiEnabled()).thenReturn(false);
+
mAccessPointsChangedLatch = new CountDownLatch(1);
tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
-
- assertTrue(mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
+ assertThat(mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
assertThat(tracker.getAccessPoints()).isEmpty();
}
@@ -825,7 +786,7 @@
@Test
public void onConnectedChangedCallback_shouldNotBeInvokedWhenNoStateChange() throws Exception {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
- verify(mockWifiListener, times(1)).onConnectedChanged();
+ verify(mockWifiListenerExecutor, times(1)).onConnectedChanged();
NetworkInfo networkInfo = new NetworkInfo(
ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
@@ -835,14 +796,13 @@
intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
tracker.mReceiver.onReceive(mContext, intent);
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
- verify(mockWifiListener, times(1)).onConnectedChanged();
+ verify(mockWifiListenerExecutor, times(1)).onConnectedChanged();
}
@Test
public void onConnectedChangedCallback_shouldBeInvokedWhenStateChanges() throws Exception {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
- verify(mockWifiListener, times(1)).onConnectedChanged();
+ verify(mockWifiListenerExecutor, times(1)).onConnectedChanged();
NetworkInfo networkInfo = new NetworkInfo(
ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
@@ -853,9 +813,8 @@
intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
tracker.mReceiver.onReceive(mContext, intent);
- waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
assertThat(tracker.isConnected()).isFalse();
- verify(mockWifiListener, times(2)).onConnectedChanged();
+ verify(mockWifiListenerExecutor, times(2)).onConnectedChanged();
}
@Test
diff --git a/packages/SettingsProvider/res/values-as/defaults.xml b/packages/SettingsProvider/res/values-as/defaults.xml
new file mode 100644
index 0000000..ea05c92
--- /dev/null
+++ b/packages/SettingsProvider/res/values-as/defaults.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (c) 2009, 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="def_device_name" msgid="6309317409634339402">"%1$s %2$s"</string>
+ <string name="def_device_name_simple" msgid="9037785625140748221">"%1$s"</string>
+ <string name="def_nfc_payment_component" msgid="5861297439873026958"></string>
+ <string name="def_backup_manager_constants" msgid="75273734665044867"></string>
+ <string name="def_backup_local_transport_parameters" msgid="303005414813191641"></string>
+</resources>
diff --git a/packages/SettingsProvider/res/values-as/strings.xml b/packages/SettingsProvider/res/values-as/strings.xml
new file mode 100644
index 0000000..233253e
--- /dev/null
+++ b/packages/SettingsProvider/res/values-as/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (c) 2007, 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label" msgid="4567566098528588863">"ছেটিংছসমূহৰ সঞ্চয়াগাৰ"</string>
+</resources>
diff --git a/packages/SettingsProvider/res/values-or/defaults.xml b/packages/SettingsProvider/res/values-or/defaults.xml
new file mode 100644
index 0000000..ea05c92
--- /dev/null
+++ b/packages/SettingsProvider/res/values-or/defaults.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (c) 2009, 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="def_device_name" msgid="6309317409634339402">"%1$s %2$s"</string>
+ <string name="def_device_name_simple" msgid="9037785625140748221">"%1$s"</string>
+ <string name="def_nfc_payment_component" msgid="5861297439873026958"></string>
+ <string name="def_backup_manager_constants" msgid="75273734665044867"></string>
+ <string name="def_backup_local_transport_parameters" msgid="303005414813191641"></string>
+</resources>
diff --git a/packages/SettingsProvider/res/values-or/strings.xml b/packages/SettingsProvider/res/values-or/strings.xml
new file mode 100644
index 0000000..04f68c8
--- /dev/null
+++ b/packages/SettingsProvider/res/values-or/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (c) 2007, 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.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label" msgid="4567566098528588863">"ସେଟିଙ୍ଗ ଷ୍ଟୋରେଜ୍"</string>
+</resources>
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
index 255e377..93df340 100644
--- a/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
+++ b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
@@ -14,104 +14,110 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
- <vector android:height="24dp"
+ <vector android:name="root"
android:width="24dp"
- android:viewportHeight="102"
- android:viewportWidth="102"
- android:tint="?attr/singleToneColor">
- <group android:name="_R_G">
- <group android:name="_R_G_L_0_G" android:translateX="53.086" android:translateY="48.907000000000004" android:pivotX="-2.083" android:pivotY="2.083" android:rotation="90">
- <group android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" android:rotation="100.1" android:scaleX="0.7979999999999999" android:scaleY="0.7979999999999999">
- <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M17.15 -37.84 C30.19,-31.91 39.52,-19.62 41.9,-4.86 C42.15,-3.39 43.45,-2.31 44.95,-2.31 C46.88,-2.31 48.34,-4.06 48.05,-5.94 C44.37,-27.64 27.64,-45.91 0.84,-48.09 C-1.08,-48.25 -2.17,-45.91 -0.83,-44.53 C-0.83,-44.53 9.87,-33.83 9.87,-33.83 C10.67,-33.04 11.92,-33.04 12.76,-33.79 C12.76,-33.79 17.15,-37.84 17.15,-37.84c "/>
- </group>
- <group android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0" android:rotation="87.2" android:scaleX="0.77" android:scaleY="0.77">
- <path android:name="_R_G_L_0_G_D_1_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-21.32 42.01 C-34.36,36.07 -43.68,23.78 -46.07,9.02 C-46.33,7.55 -47.62,6.47 -49.12,6.47 C-51.04,6.47 -52.51,8.23 -52.21,10.11 C-48.53,31.81 -31.81,50.08 -5.01,52.25 C-3.09,52.42 -2,50.08 -3.34,48.7 C-3.34,48.7 -14.04,38 -14.04,38 C-14.84,37.21 -16.11,37.19 -16.93,37.96 C-16.93,37.96 -21.32,42.01 -21.32,42.01c "/>
- </group>
- <path android:name="_R_G_L_0_G_D_2_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.77 9.4 C40.77,9.4 -9.4,-40.77 -9.4,-40.77 C-11.91,-43.28 -15.67,-43.28 -18.18,-40.77 C-18.18,-40.77 -44.94,-14.01 -44.94,-14.01 C-47.45,-11.5 -47.45,-7.74 -44.94,-5.23 C-44.94,-5.23 5.23,44.94 5.23,44.94 C7.74,47.45 11.51,47.45 14.01,44.94 C14.01,44.94 40.77,18.18 40.77,18.18 C43.28,15.67 43.28,11.91 40.77,9.4c M3.85 34.82 C3.85,34.82 -34.4,-3.44 -34.4,-3.44 C-34.4,-3.44 -7.64,-30.19 -7.64,-30.19 C-7.64,-30.19 30.61,8.06 30.61,8.06 C30.61,8.06 3.85,34.82 3.85,34.82c "/>
- </group>
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <group android:name="icon" android:pivotX="12" android:pivotY="12">
+ <!-- Tint color to be set directly -->
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
</group>
- <group android:name="time_group"/>
</vector>
</aapt:attr>
- <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
+ <!-- Repeat all animations 3 times but don't fade out at the end -->
+ <target android:name="root">
<aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="rotation" android:duration="333" android:startOffset="0" android:valueFrom="100.1" android:valueTo="0" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
+ <set android:ordering="sequentially">
+ <!-- Linear fade in-->
+ <objectAnimator android:propertyName="alpha"
+ android:duration="100"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:interpolator="@android:anim/linear_interpolator" />
+ <!-- Linear fade out -->
+ <objectAnimator android:propertyName="alpha"
+ android:duration="100"
+ android:startOffset="1700"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:interpolator="@android:anim/linear_interpolator"/>
+ <!-- Linear fade in-->
+ <objectAnimator android:propertyName="alpha"
+ android:duration="100"
+ android:startOffset="100"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:interpolator="@android:anim/linear_interpolator" />
+ <!-- Linear fade out -->
+ <objectAnimator android:propertyName="alpha"
+ android:duration="100"
+ android:startOffset="1700"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:interpolator="@android:anim/linear_interpolator"/>
+ <!-- Linear fade in-->
+ <objectAnimator android:propertyName="alpha"
+ android:duration="100"
+ android:startOffset="100"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:interpolator="@android:anim/linear_interpolator" />
</set>
</aapt:attr>
</target>
-
- <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
+ <target android:name="icon">
<aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="0" android:valueFrom="0.798" android:valueTo="1" android:valueType="floatType">
+ <set android:ordering="sequentially">
+ <!-- Icon rotation with start timing offset after fade in -->
+ <objectAnimator android:propertyName="rotation"
+ android:startOffset="100"
+ android:duration="600"
+ android:valueFrom="?attr/rotateButtonStartAngle"
+ android:valueTo="?attr/rotateButtonEndAngle">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="0" android:valueFrom="0.798" android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="rotation" android:duration="333" android:startOffset="0" android:valueFrom="87.2" android:valueTo="0" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
+ <!-- Reset rotation position for fade in -->
+ <objectAnimator android:propertyName="rotation"
+ android:startOffset="1300"
+ android:duration="100"
+ android:valueFrom="?attr/rotateButtonStartAngle"
+ android:valueTo="?attr/rotateButtonStartAngle"/>
- <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="0" android:valueFrom="0.77" android:valueTo="1" android:valueType="floatType">
+ <!-- Icon rotation with start timing offset after fade in -->
+ <objectAnimator android:propertyName="rotation"
+ android:duration="600"
+ android:valueFrom="?attr/rotateButtonStartAngle"
+ android:valueTo="?attr/rotateButtonEndAngle">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="0" android:valueFrom="0.77" android:valueTo="1" android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="rotation" android:duration="333" android:startOffset="0" android:valueFrom="90" android:valueTo="0" android:valueType="floatType">
+ <!-- Reset rotation position for fade in -->
+ <objectAnimator android:propertyName="rotation"
+ android:startOffset="1300"
+ android:duration="100"
+ android:valueFrom="?attr/rotateButtonStartAngle"
+ android:valueTo="?attr/rotateButtonStartAngle"/>
+
+ <!-- Icon rotation with start timing offset after fade in -->
+ <objectAnimator android:propertyName="rotation"
+ android:duration="600"
+ android:valueFrom="?attr/rotateButtonStartAngle"
+ android:valueTo="?attr/rotateButtonEndAngle">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
-
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
- </set>
- </aapt:attr>
- </target>
</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/car_left_navigation_bar.xml b/packages/SystemUI/res/layout/car_left_navigation_bar.xml
index 866b5a5..18301a8 100644
--- a/packages/SystemUI/res/layout/car_left_navigation_bar.xml
+++ b/packages/SystemUI/res/layout/car_left_navigation_bar.xml
@@ -51,7 +51,7 @@
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- systemui:intent="intent:#Intent;action=android.car.intent.action.SHOW_HVAC_CONTROLS;end"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
systemui:broadcast="true"
android:src="@drawable/car_ic_hvac"
android:background="?android:attr/selectableItemBackground"
diff --git a/packages/SystemUI/res/layout/car_navigation_bar.xml b/packages/SystemUI/res/layout/car_navigation_bar.xml
index 4666c60..9ff16a2 100644
--- a/packages/SystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/SystemUI/res/layout/car_navigation_bar.xml
@@ -49,7 +49,7 @@
android:id="@+id/hvac"
android:layout_height="match_parent"
android:layout_width="wrap_content"
- systemui:intent="intent:#Intent;action=android.car.intent.action.SHOW_HVAC_CONTROLS;end"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
systemui:broadcast="true"
android:src="@drawable/car_ic_hvac"
android:background="?android:attr/selectableItemBackground"
diff --git a/packages/SystemUI/res/layout/car_right_navigation_bar.xml b/packages/SystemUI/res/layout/car_right_navigation_bar.xml
index 99ab802..99bd23c 100644
--- a/packages/SystemUI/res/layout/car_right_navigation_bar.xml
+++ b/packages/SystemUI/res/layout/car_right_navigation_bar.xml
@@ -51,7 +51,7 @@
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- systemui:intent="intent:#Intent;action=android.car.intent.action.SHOW_HVAC_CONTROLS;end"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
systemui:broadcast="true"
android:src="@drawable/car_ic_hvac"
android:background="?android:attr/selectableItemBackground"
diff --git a/packages/SystemUI/res/layout/quick_settings_header.xml b/packages/SystemUI/res/layout/quick_settings_header.xml
new file mode 100644
index 0000000..43197c4
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_header.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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
+ -->
+<com.android.systemui.qs.QSTooltipView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_header_tooltip_height"
+ android:alpha="0"
+ android:gravity="center_horizontal|bottom"
+ android:visibility="invisible">
+
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/quick_settings_header_onboarding_text"
+ android:textAppearance="@style/TextAppearance.QS.TileLabel"
+ android:textColor="?android:attr/colorAccent" />
+
+</com.android.systemui.qs.QSTooltipView>
diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml
deleted file mode 100644
index 5074682..0000000
--- a/packages/SystemUI/res/layout/rotate_suggestion.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<com.android.systemui.statusbar.policy.KeyButtonView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/rotate_suggestion"
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="2dp"
- android:visibility="invisible"
- android:scaleType="centerInside"
- android:contentDescription="@string/accessibility_rotate_button"
-/>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index a5e37d5..13ca114 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -40,7 +40,7 @@
<dimen name="battery_detail_graph_space_top">27dp</dimen>
<dimen name="battery_detail_graph_space_bottom">27dp</dimen>
- <dimen name="qs_tile_margin_top">16dp</dimen>
+ <dimen name="qs_tile_margin_top">32dp</dimen>
<dimen name="qs_brightness_padding_top">6dp</dimen>
<dimen name="qs_detail_margin_top">28dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index f0a5fe4..b11266a 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -136,5 +136,9 @@
<attr name="singleLineButtonPaddingHorizontal" format="dimension" />
<attr name="doubleLineButtonPaddingHorizontal" format="dimension" />
</declare-styleable>
+
+ <!-- Used to style rotate suggestion button AVD animations -->
+ <attr name="rotateButtonStartAngle" format="float" />
+ <attr name="rotateButtonEndAngle" format="float" />
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e679fcd..627b4bc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -290,7 +290,7 @@
<dimen name="qs_tile_height">106dp</dimen>
<dimen name="qs_tile_margin">19dp</dimen>
- <dimen name="qs_tile_margin_top">16dp</dimen>
+ <dimen name="qs_tile_margin_top">32dp</dimen>
<dimen name="qs_quick_tile_size">48dp</dimen>
<dimen name="qs_quick_tile_padding">12dp</dimen>
<dimen name="qs_header_gear_translation">16dp</dimen>
@@ -309,6 +309,7 @@
<dimen name="qs_tile_padding_bottom">16dp</dimen>
<dimen name="qs_tile_spacing">4dp</dimen>
<dimen name="qs_panel_padding_bottom">0dp</dimen>
+ <dimen name="qs_panel_padding_top">32dp</dimen>
<dimen name="qs_detail_header_height">56dp</dimen>
<dimen name="qs_detail_header_padding">0dp</dimen>
<dimen name="qs_detail_image_width">56dp</dimen>
@@ -333,6 +334,9 @@
<dimen name="qs_detail_item_icon_width">32dp</dimen>
<dimen name="qs_detail_item_icon_marginStart">0dp</dimen>
<dimen name="qs_detail_item_icon_marginEnd">20dp</dimen>
+ <dimen name="qs_header_padding_start">16dp</dimen>
+ <dimen name="qs_header_padding_end">24dp</dimen>
+ <dimen name="qs_header_tooltip_height">32dp</dimen>
<dimen name="qs_footer_padding_start">16dp</dimen>
<dimen name="qs_footer_padding_end">24dp</dimen>
<dimen name="qs_footer_icon_size">16dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 2d30f4c..0e92c60 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -47,7 +47,6 @@
<item type="id" name="qs_icon_tag"/>
<item type="id" name="qs_slash_tag"/>
<item type="id" name="scrim"/>
- <item type="id" name="scrim_target"/>
<item type="id" name="scrim_alpha_start"/>
<item type="id" name="scrim_alpha_end"/>
<item type="id" name="notification_power"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0b5b7bd..71a3ac5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -668,6 +668,8 @@
<!-- Textual description of Ethernet connections -->
<string name="ethernet_label">Ethernet</string>
+ <!-- QuickSettings: Onboarding text that introduces users to long press on an option in order to view the option's menu in Settings [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_header_onboarding_text">Press & hold on the icons for more options</string>
<!-- QuickSettings: Do not disturb [CHAR LIMIT=NONE] -->
<string name="quick_settings_dnd_label">Do not disturb</string>
<!-- QuickSettings: Do not disturb - Priority only [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d2ed4d1..a01f71a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -485,4 +485,25 @@
<item name="android:colorBackground">?android:attr/colorSecondary</item>
</style>
+ <!-- Used to style rotate suggestion button AVD animations -->
+ <style name="RotateButtonCCWStart0">
+ <item name="rotateButtonStartAngle">0</item>
+ <item name="rotateButtonEndAngle">-90</item>
+ </style>
+
+ <style name="RotateButtonCCWStart90">
+ <item name="rotateButtonStartAngle">90</item>
+ <item name="rotateButtonEndAngle">0</item>
+ </style>
+
+ <style name="RotateButtonCWStart0">
+ <item name="rotateButtonStartAngle">0</item>
+ <item name="rotateButtonEndAngle">90</item>
+ </style>
+
+ <style name="RotateButtonCWStart90">
+ <item name="rotateButtonStartAngle">90</item>
+ <item name="rotateButtonEndAngle">180</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index adb4e33..8b57740 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -47,6 +47,7 @@
Key.QS_INVERT_COLORS_ADDED,
Key.QS_WORK_ADDED,
Key.QS_NIGHTDISPLAY_ADDED,
+ Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
Key.SEEN_MULTI_USER,
Key.NUM_APPS_LAUNCHED,
Key.HAS_SEEN_RECENTS_ONBOARDING,
@@ -76,6 +77,11 @@
String QS_WORK_ADDED = "QsWorkAdded";
@Deprecated
String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded";
+ /**
+ * Used for tracking how many times the user has seen the long press tooltip in the Quick
+ * Settings panel.
+ */
+ String QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT = "QsLongPressTooltipShownCount";
String SEEN_MULTI_USER = "HasSeenMultiUser";
String NUM_APPS_LAUNCHED = "NumAppsLaunched";
String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index c3f7eb1..175cddc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -44,6 +44,7 @@
public static final float EXPANDED_TILE_DELAY = .86f;
+
private final ArrayList<View> mAllViews = new ArrayList<>();
/**
* List of {@link View}s representing Quick Settings that are being animated from the quick QS
@@ -65,6 +66,10 @@
private TouchAnimator mNonfirstPageDelayedAnimator;
private TouchAnimator mBrightnessAnimator;
+ /**
+ * Whether we're in the middle of animating between the collapsed and expanded states.
+ */
+ private boolean mIsAnimating;
private boolean mOnKeyguard;
private boolean mAllowFancy;
@@ -89,6 +94,9 @@
Log.w(TAG, "QS Not using page layout");
}
panel.setPageListener(this);
+
+ // At time of creation, the QS panel is never animating.
+ mIsAnimating = false;
}
public void onRtlChanged() {
@@ -243,6 +251,11 @@
} else {
mBrightnessAnimator = null;
}
+ View headerView = mQsPanel.getHeaderView();
+ if (headerView!= null) {
+ firstPageBuilder.addFloat(headerView, "translationY", heightDiff, 0);
+ mAllViews.add(headerView);
+ }
mFirstPageAnimator = firstPageBuilder
.setListener(this)
.build();
@@ -329,11 +342,21 @@
@Override
public void onAnimationAtStart() {
+ if (mIsAnimating) {
+ mQsPanel.onCollapse();
+ }
+ mIsAnimating = false;
+
mQuickQsPanel.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationAtEnd() {
+ if (mIsAnimating) {
+ mQsPanel.onExpanded();
+ }
+ mIsAnimating = false;
+
mQuickQsPanel.setVisibility(View.INVISIBLE);
final int N = mQuickQsViews.size();
for (int i = 0; i < N; i++) {
@@ -343,6 +366,11 @@
@Override
public void onAnimationStarted() {
+ if (!mIsAnimating) {
+ mQsPanel.onAnimating();
+ }
+ mIsAnimating = true;
+
mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE);
if (mOnFirstPage) {
final int N = mQuickQsViews.size();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index cdcc5e6..476cb40 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -18,6 +18,7 @@
import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
@@ -57,6 +58,7 @@
public class QSPanel extends LinearLayout implements Tunable, Callback, BrightnessMirrorListener {
public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
+ public static final String QS_SHOW_LONG_PRESS_TOOLTIP = "qs_show_long_press";
protected final Context mContext;
protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
@@ -72,6 +74,7 @@
private BrightnessController mBrightnessController;
protected QSTileHost mHost;
+ protected QSTooltipView mTooltipView;
protected QSSecurityFooter mFooter;
private boolean mGridContentVisible = true;
@@ -93,6 +96,11 @@
setOrientation(VERTICAL);
+ mTooltipView = (QSTooltipView) LayoutInflater.from(mContext)
+ .inflate(R.layout.quick_settings_header, this, false);
+
+ addView(mTooltipView);
+
mBrightnessView = LayoutInflater.from(mContext).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
addView(mBrightnessView);
@@ -142,7 +150,10 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- Dependency.get(TunerService.class).addTunable(this, QS_SHOW_BRIGHTNESS);
+ final TunerService tunerService = Dependency.get(TunerService.class);
+ tunerService.addTunable(this, QS_SHOW_BRIGHTNESS);
+ tunerService.addTunable(this, QS_SHOW_LONG_PRESS_TOOLTIP);
+
if (mHost != null) {
setTiles(mHost.getTiles());
}
@@ -174,11 +185,16 @@
@Override
public void onTuningChanged(String key, String newValue) {
if (QS_SHOW_BRIGHTNESS.equals(key)) {
- mBrightnessView.setVisibility(newValue == null || Integer.parseInt(newValue) != 0
- ? VISIBLE : GONE);
+ updateViewVisibilityForTuningValue(mBrightnessView, newValue);
+ } else if (QS_SHOW_LONG_PRESS_TOOLTIP.equals(key)) {
+ updateViewVisibilityForTuningValue(mTooltipView, newValue);
}
}
+ private void updateViewVisibilityForTuningValue(View view, @Nullable String newValue) {
+ view.setVisibility(newValue == null || Integer.parseInt(newValue) != 0 ? VISIBLE : GONE);
+ }
+
public void openDetails(String subPanel) {
QSTile tile = getTile(subPanel);
showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0});
@@ -213,6 +229,10 @@
return mBrightnessView;
}
+ View getHeaderView() {
+ return mTooltipView;
+ }
+
public void setCallback(QSDetail.Callback callback) {
mCallback = callback;
}
@@ -269,6 +289,21 @@
if (mCustomizePanel != null && mCustomizePanel.isShown()) {
mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2);
}
+
+ // Instantly hide the header here since we don't want it to still be animating.
+ mTooltipView.setVisibility(View.INVISIBLE);
+ }
+
+ /**
+ * Called when the panel is fully animated out/expanded. This is different from the state
+ * tracked by {@link #mExpanded}, which only checks if the panel is even partially pulled out.
+ */
+ public void onExpanded() {
+ mTooltipView.fadeIn();
+ }
+
+ public void onAnimating() {
+ mTooltipView.fadeOut();
}
public void setExpanded(boolean expanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java
new file mode 100644
index 0000000..d1f9741
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTooltipView.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.qs;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.systemui.Prefs;
+
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Tooltip/header view for the Quick Settings panel.
+ */
+public class QSTooltipView extends LinearLayout {
+
+ private static final int FADE_ANIMATION_DURATION_MS = 300;
+ private static final long AUTO_FADE_OUT_DELAY_MS = TimeUnit.SECONDS.toMillis(6);
+ private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0;
+ public static final int MAX_TOOLTIP_SHOWN_COUNT = 3;
+
+ private final Handler mHandler = new Handler();
+ private final Runnable mAutoFadeOutRunnable = () -> fadeOut();
+
+ private int mShownCount;
+
+ public QSTooltipView(Context context) {
+ this(context, null);
+ }
+
+ public QSTooltipView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mShownCount = getStoredShownCount();
+ }
+
+ /** Returns the latest stored tooltip shown count from SharedPreferences. */
+ private int getStoredShownCount() {
+ return Prefs.getInt(
+ mContext,
+ Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
+ TOOLTIP_NOT_YET_SHOWN_COUNT);
+ }
+
+ /**
+ * Fades in the header view if we can show the tooltip - short circuits any running animation.
+ */
+ public void fadeIn() {
+ if (mShownCount < MAX_TOOLTIP_SHOWN_COUNT) {
+ animate().cancel();
+ setVisibility(View.VISIBLE);
+ animate()
+ .alpha(1f)
+ .setDuration(FADE_ANIMATION_DURATION_MS)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHandler.postDelayed(mAutoFadeOutRunnable, AUTO_FADE_OUT_DELAY_MS);
+ }
+ })
+ .start();
+
+ // Increment and drop the shown count in prefs for the next time we're deciding to
+ // fade in the tooltip. We first sanity check that the tooltip count hasn't changed yet
+ // in prefs (say, from a long press).
+ if (getStoredShownCount() <= mShownCount) {
+ Prefs.putInt(mContext, Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT, ++mShownCount);
+ }
+ }
+ }
+
+ /**
+ * Fades out the header view if it's partially visible - short circuits any running animation.
+ */
+ public void fadeOut() {
+ animate().cancel();
+ if (getVisibility() == View.VISIBLE && getAlpha() != 0f) {
+ mHandler.removeCallbacks(mAutoFadeOutRunnable);
+ animate()
+ .alpha(0f)
+ .setDuration(FADE_ANIMATION_DURATION_MS)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ perhapsMakeViewInvisible();
+ }
+ })
+ .start();
+ } else {
+ perhapsMakeViewInvisible();
+ }
+ }
+
+ /**
+ * Only update visibility if the view is currently being shown. Otherwise, it's already been
+ * hidden by some other manner.
+ */
+ private void perhapsMakeViewInvisible() {
+ if (getVisibility() == View.VISIBLE) {
+ setVisibility(View.INVISIBLE);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index bdda3b9..f0684e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -123,9 +123,8 @@
@Override
public void onTuningChanged(String key, String newValue) {
- // No tunings for you.
- if (key.equals(QS_SHOW_BRIGHTNESS)) {
- // No Brightness for you.
+ if (QS_SHOW_BRIGHTNESS.equals(key) || QS_SHOW_LONG_PRESS_TOOLTIP.equals(key)) {
+ // No Brightness or Tooltip for you!
super.onTuningChanged(key, "0");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 65135ab..23faa55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -99,7 +99,11 @@
record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight));
previousView = record.tileView.updateAccessibilityOrder(previousView);
}
- int height = (mCellHeight + mCellMargin) * rows + (mCellMarginTop - mCellMargin);
+
+ // Only include the top margin in our measurement if we have more than 1 row to show.
+ // Otherwise, don't add the extra margin buffer at top.
+ int height = (mCellHeight + mCellMargin) * rows +
+ (rows != 0 ? (mCellMarginTop - mCellMargin) : 0);
if (height < 0) height = 0;
setMeasuredDimension(width, height);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
index 37f2528..6263efa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
@@ -107,7 +107,7 @@
void onAnimationAtStart();
/**
- * Called when the animator moves into a position of "0". Start and end delays are
+ * Called when the animator moves into a position of "1". Start and end delays are
* taken into account, so this position may cover a range of fractional inputs.
*/
void onAnimationAtEnd();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 72592829..016cbd6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -42,6 +42,7 @@
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
@@ -49,6 +50,7 @@
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.PagedTileLayout.TilePage;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QSTooltipView;
import java.util.ArrayList;
@@ -191,6 +193,11 @@
public void longClick() {
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.LONG_CLICK);
+
+ Prefs.putInt(
+ mContext,
+ Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
+ QSTooltipView.MAX_TOOLTIP_SHOWN_COUNT);
}
public LogMaker populate(LogMaker logMaker) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 2723df7..bc2dff9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -82,7 +82,6 @@
import com.android.systemui.statusbar.stack.AnimationProperties;
import com.android.systemui.statusbar.stack.ExpandableViewState;
import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackScrollState;
import java.util.ArrayList;
@@ -181,6 +180,7 @@
private AboveShelfChangedListener mAboveShelfChangedListener;
private HeadsUpManager mHeadsUpManager;
private View mHelperButton;
+ private boolean mChildIsExpanding;
private boolean mJustClicked;
private boolean mIconAnimationRunning;
@@ -575,8 +575,13 @@
* @param isChildInGroup Is this notification now in a group
* @param parent the new parent notification
*/
- public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {;
+ public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {
boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
+ if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {
+ mNotificationParent.setChildIsExpanding(false);
+ mNotificationParent.setExtraWidthForClipping(0.0f);
+ mNotificationParent.setMinimumHeightForClipping(0);
+ }
mNotificationParent = childInGroup ? parent : null;
mPrivateLayout.setIsChildInGroup(childInGroup);
mNotificationInflater.setIsChildInGroup(childInGroup);
@@ -651,10 +656,11 @@
visualStabilityManager, callback);
}
- public void getChildrenStates(StackScrollState resultState) {
+ public void getChildrenStates(StackScrollState resultState,
+ AmbientState ambientState) {
if (mIsSummaryWithChildren) {
ExpandableViewState parentState = resultState.getViewStateForView(this);
- mChildrenContainer.getState(resultState, parentState);
+ mChildrenContainer.getState(resultState, parentState, ambientState);
}
}
@@ -1601,28 +1607,52 @@
if (params == null) {
return;
}
- setTranslationY(params.getTop());
float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
params.getProgress(0, 50));
float translationZ = MathUtils.lerp(params.getStartTranslationZ(),
mNotificationLaunchHeight,
zProgress);
setTranslationZ(translationZ);
+ float extraWidthForClipping = params.getWidth() - getWidth()
+ + MathUtils.lerp(0, mOutlineRadius * 2, params.getProgress());
+ setExtraWidthForClipping(extraWidthForClipping);
+ int top = params.getTop();
+ float interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress());
+ int startClipTopAmount = params.getStartClipTopAmount();
+ if (mNotificationParent != null) {
+ top -= mNotificationParent.getTranslationY();
+ mNotificationParent.setTranslationZ(translationZ);
+ int parentStartClipTopAmount = params.getParentStartClipTopAmount();
+ if (startClipTopAmount != 0) {
+ int clipTopAmount = (int) MathUtils.lerp(parentStartClipTopAmount,
+ parentStartClipTopAmount - startClipTopAmount,
+ interpolation);
+ mNotificationParent.setClipTopAmount(clipTopAmount);
+ }
+ mNotificationParent.setExtraWidthForClipping(extraWidthForClipping);
+ mNotificationParent.setMinimumHeightForClipping(params.getHeight()
+ + mNotificationParent.getActualHeight());
+ } else if (startClipTopAmount != 0) {
+ int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, interpolation);
+ setClipTopAmount(clipTopAmount);
+ }
+ setTranslationY(top);
setActualHeight(params.getHeight());
+
mBackgroundNormal.setExpandAnimationParams(params);
}
public void setExpandAnimationRunning(boolean expandAnimationRunning) {
+ View contentView;
+ if (mIsSummaryWithChildren) {
+ contentView = mChildrenContainer;
+ } else {
+ contentView = getShowingLayout();
+ }
+ if (mGuts != null && mGuts.isExposed()) {
+ contentView = mGuts;
+ }
if (expandAnimationRunning) {
- View contentView;
- if (mIsSummaryWithChildren) {
- contentView = mChildrenContainer;
- } else {
- contentView = getShowingLayout();
- }
- if (mGuts != null && mGuts.isExposed()) {
- contentView = mGuts;
- }
contentView.animate()
.alpha(0f)
.setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT)
@@ -1637,15 +1667,35 @@
if (mGuts != null) {
mGuts.setAlpha(1.0f);
}
+ if (contentView != null) {
+ contentView.setAlpha(1.0f);
+ }
+ setExtraWidthForClipping(0.0f);
+ if (mNotificationParent != null) {
+ mNotificationParent.setExtraWidthForClipping(0.0f);
+ mNotificationParent.setMinimumHeightForClipping(0);
+ }
+ }
+ if (mNotificationParent != null) {
+ mNotificationParent.setChildIsExpanding(mExpandAnimationRunning);
}
updateChildrenVisibility();
updateClipping();
mBackgroundNormal.setExpandAnimationRunning(expandAnimationRunning);
}
+ private void setChildIsExpanding(boolean isExpanding) {
+ mChildIsExpanding = isExpanding;
+ }
+
+ @Override
+ public boolean hasExpandingChild() {
+ return mChildIsExpanding;
+ }
+
@Override
protected boolean shouldClipToActualHeight() {
- return super.shouldClipToActualHeight() && !mExpandAnimationRunning;
+ return super.shouldClipToActualHeight() && !mExpandAnimationRunning && !mChildIsExpanding;
}
@Override
@@ -1947,18 +1997,10 @@
}
}
- private void updateMaxHeights() {
+ public void updateMaxHeights() {
int intrinsicBefore = getIntrinsicHeight();
- View expandedChild = mPrivateLayout.getExpandedChild();
- if (expandedChild == null) {
- expandedChild = mPrivateLayout.getContractedChild();
- }
- mMaxExpandHeight = expandedChild.getHeight();
- View headsUpChild = mPrivateLayout.getHeadsUpChild();
- if (headsUpChild == null) {
- headsUpChild = mPrivateLayout.getContractedChild();
- }
- mHeadsUpHeight = headsUpChild.getHeight();
+ mMaxExpandHeight = mPrivateLayout.getExpandHeight();
+ mHeadsUpHeight = mPrivateLayout.getHeadsUpHeight();
if (intrinsicBefore != getIntrinsicHeight()) {
notifyHeightChanged(true /* needsAnimation */);
}
@@ -2245,7 +2287,7 @@
mGuts.setClipBottomAmount(clipBottomAmount);
}
}
- if (mChildrenContainer != null) {
+ if (mChildrenContainer != null && !mChildIsExpanding) {
// We have to update this even if it hasn't changed, since the children locations can
// have changed
mChildrenContainer.setClipBottomAmount(clipBottomAmount);
@@ -2453,7 +2495,7 @@
public boolean isAboveShelf() {
return !isOnKeyguard()
&& (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)
- || mExpandAnimationRunning);
+ || mExpandAnimationRunning || mChildIsExpanding);
}
public void setShowAmbient(boolean showAmbient) {
@@ -2478,7 +2520,7 @@
return true;
}
} else if (child == mChildrenContainer) {
- if (isClippingNeeded() || !hasNoRounding()) {
+ if (!mChildIsExpanding && (isClippingNeeded() || !hasNoRounding())) {
return true;
}
} else if (child instanceof NotificationGuts) {
@@ -2544,11 +2586,19 @@
if (row.isExpandAnimationRunning()) {
return;
}
+ handleFixedTranslationZ(row);
super.applyToView(view);
row.applyChildrenState(mOverallState);
}
}
+ private void handleFixedTranslationZ(ExpandableNotificationRow row) {
+ if (row.hasExpandingChild()) {
+ zTranslation = row.getTranslationZ();
+ clipTopAmount = row.getClipTopAmount();
+ }
+ }
+
@Override
protected void onYTranslationAnimationFinished(View view) {
super.onYTranslationAnimationFinished(view);
@@ -2567,6 +2617,7 @@
if (row.isExpandAnimationRunning()) {
return;
}
+ handleFixedTranslationZ(row);
super.animateTo(child, properties);
row.startChildAnimation(mOverallState, properties);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 66b3a75..8bc2201 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -61,7 +61,7 @@
private final Path mClipPath = new Path();
private boolean mCustomOutline;
private float mOutlineAlpha = -1f;
- private float mOutlineRadius;
+ protected float mOutlineRadius;
private boolean mAlwaysRoundBothCorners;
private Path mTmpPath = new Path();
private Path mTmpPath2 = new Path();
@@ -78,6 +78,8 @@
protected boolean mShouldTranslateContents;
private boolean mClipRoundedToClipTopAmount;
private float mDistanceToTopRoundness = -1;
+ private float mExtraWidthForClipping;
+ private int mMinimumHeightForClipping = 0;
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
@@ -202,11 +204,11 @@
canvas.save();
Path intersectPath = null;
if (mClipRoundedToClipTopAmount) {
- int left = 0;
+ int left = (int) (- mExtraWidthForClipping / 2.0f);
int top = (int) (mClipTopAmount - mDistanceToTopRoundness);
- int right = getWidth();
- int bottom = (int) Math.max(getActualHeight() - mClipBottomAmount,
- top + mOutlineRadius);
+ int right = getWidth() + (int) (mExtraWidthForClipping + left);
+ int bottom = (int) Math.max(mMinimumHeightForClipping,
+ Math.max(getActualHeight() - mClipBottomAmount, top + mOutlineRadius));
ExpandableOutlineView.getRoundedRectPath(left, top, right, bottom, mOutlineRadius,
0.0f,
mClipPath);
@@ -234,6 +236,14 @@
return result;
}
+ public void setExtraWidthForClipping(float extraWidthForClipping) {
+ mExtraWidthForClipping = extraWidthForClipping;
+ }
+
+ public void setMinimumHeightForClipping(int minimumHeightForClipping) {
+ mMinimumHeightForClipping = minimumHeightForClipping;
+ }
+
@Override
public void setDistanceToTopRoundness(float distanceToTopRoundness) {
super.setDistanceToTopRoundness(distanceToTopRoundness);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 1496a41..204adc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -543,6 +543,10 @@
return false;
}
+ public boolean hasExpandingChild() {
+ return false;
+ }
+
/**
* A listener notifying when {@link #getActualHeight} changes.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index c4d0b79..91960df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -143,6 +143,8 @@
private int mClipBottomAmount;
private boolean mIsLowPriority;
private boolean mIsContentExpandable;
+ private boolean mRemoteInputVisible;
+ private int mUnrestrictedContentHeight;
public NotificationContentView(Context context, AttributeSet attrs) {
@@ -293,6 +295,24 @@
setMeasuredDimension(width, ownHeight);
}
+ /**
+ * Get the extra height that needs to be added to the notification height for a given
+ * {@link RemoteInputView}.
+ * This is needed when the user is inline replying in order to ensure that the reply bar has
+ * enough padding.
+ *
+ * @param remoteInput The remote input to check.
+ * @return The extra height needed.
+ */
+ private int getExtraRemoteInputHeight(RemoteInputView remoteInput) {
+ if (remoteInput != null && remoteInput.getVisibility() == VISIBLE
+ && remoteInput.isActive()) {
+ return getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_content_margin);
+ }
+ return 0;
+ }
+
private boolean updateContractedHeaderWidth() {
// We need to update the expanded and the collapsed header to have exactly the same with to
// have the expand buttons laid out at the same location.
@@ -538,19 +558,23 @@
}
public void setContentHeight(int contentHeight) {
- mContentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());
+ mUnrestrictedContentHeight = Math.max(contentHeight, getMinHeight());
+ int maxContentHeight = mContainingNotification.getIntrinsicHeight()
+ - getExtraRemoteInputHeight(mExpandedRemoteInput)
+ - getExtraRemoteInputHeight(mHeadsUpRemoteInput);
+ mContentHeight = Math.min(mUnrestrictedContentHeight, maxContentHeight);
selectLayout(mAnimate /* animate */, false /* force */);
int minHeightHint = getMinContentHeightHint();
NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
if (wrapper != null) {
- wrapper.setContentHeight(mContentHeight, minHeightHint);
+ wrapper.setContentHeight(mUnrestrictedContentHeight, minHeightHint);
}
wrapper = getVisibleWrapper(mTransformationStartVisibleType);
if (wrapper != null) {
- wrapper.setContentHeight(mContentHeight, minHeightHint);
+ wrapper.setContentHeight(mUnrestrictedContentHeight, minHeightHint);
}
updateClipping();
@@ -692,9 +716,9 @@
if (mContainingNotification.isShowingAmbient()) {
return getShowingAmbientView().getHeight();
} else if (mExpandedChild != null) {
- return mExpandedChild.getHeight();
+ return mExpandedChild.getHeight() + getExtraRemoteInputHeight(mExpandedRemoteInput);
} else if (mIsHeadsUp && mHeadsUpChild != null && !mContainingNotification.isOnKeyguard()) {
- return mHeadsUpChild.getHeight();
+ return mHeadsUpChild.getHeight() + getExtraRemoteInputHeight(mHeadsUpRemoteInput);
}
return mContractedChild.getHeight();
}
@@ -746,7 +770,7 @@
private void updateClipping() {
if (mClipToActualHeight) {
int top = (int) (mClipTopAmount - getTranslationY());
- int bottom = (int) (mContentHeight - mClipBottomAmount - getTranslationY());
+ int bottom = (int) (mUnrestrictedContentHeight - mClipBottomAmount - getTranslationY());
bottom = Math.max(top, bottom);
mClipBounds.set(0, top, getWidth(), bottom);
setClipBounds(mClipBounds);
@@ -790,7 +814,8 @@
}
NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
if (visibleWrapper != null) {
- visibleWrapper.setContentHeight(mContentHeight, getMinContentHeightHint());
+ visibleWrapper.setContentHeight(mUnrestrictedContentHeight,
+ getMinContentHeightHint());
}
updateBackgroundColor(animate);
}
@@ -1276,6 +1301,7 @@
mContext.getColor(R.color.remote_input_hint)));
existing.setWrapper(wrapper);
+ existing.setOnVisibilityChangedListener(this::setRemoteInputVisible);
if (existingPendingIntent != null || existing.isActive()) {
// The current action could be gone, or the pending intent no longer valid.
@@ -1576,4 +1602,31 @@
}
return null;
}
+
+ public int getExpandHeight() {
+ View expandedChild = mExpandedChild;
+ if (expandedChild == null) {
+ expandedChild = mContractedChild;
+ }
+ return expandedChild.getHeight() + getExtraRemoteInputHeight(mExpandedRemoteInput);
+ }
+
+ public int getHeadsUpHeight() {
+ View headsUpChild = mHeadsUpChild;
+ if (headsUpChild == null) {
+ headsUpChild = mContractedChild;
+ }
+ return headsUpChild.getHeight()+ getExtraRemoteInputHeight(mHeadsUpRemoteInput);
+ }
+
+ public void setRemoteInputVisible(boolean remoteInputVisible) {
+ mRemoteInputVisible = remoteInputVisible;
+ setClipChildren(!remoteInputVisible);
+ }
+
+ @Override
+ public void setClipChildren(boolean clipChildren) {
+ clipChildren = clipChildren && !mRemoteInputVisible;
+ super.setClipChildren(clipChildren);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index ef44ad1..1d9cdf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -42,8 +42,6 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
-import java.util.function.Consumer;
-
/**
* A class that allows activities to be launched in a seamless way where the notification
* transforms nicely into the starting window.
@@ -134,8 +132,24 @@
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
mParams.startPosition = mSourceNotification.getLocationOnScreen();
mParams.startTranslationZ = mSourceNotification.getTranslationZ();
+ mParams.startClipTopAmount = mSourceNotification.getClipTopAmount();
+ if (mSourceNotification.isChildInGroup()) {
+ int parentClip = mSourceNotification
+ .getNotificationParent().getClipTopAmount();
+ mParams.parentStartClipTopAmount = parentClip;
+ // We need to calculate how much the child is clipped by the parent
+ // because children always have 0 clipTopAmount
+ if (parentClip != 0) {
+ float childClip = parentClip
+ - mSourceNotification.getTranslationY();
+ if (childClip > 0.0f) {
+ mParams.startClipTopAmount = (int) Math.ceil(childClip);
+ }
+ }
+ }
int targetWidth = app.sourceContainerBounds.width();
- int notificationHeight = mSourceNotification.getActualHeight();
+ int notificationHeight = mSourceNotification.getActualHeight()
+ - mSourceNotification.getClipBottomAmount();
int notificationWidth = mSourceNotification.getWidth();
anim.setDuration(ANIMATION_DURATION);
anim.setInterpolator(Interpolators.LINEAR);
@@ -241,6 +255,8 @@
int top;
int right;
int bottom;
+ int startClipTopAmount;
+ int parentStartClipTopAmount;
public ExpandAnimationParameters() {
}
@@ -258,15 +274,32 @@
}
public int getTopChange() {
- return Math.min(top - startPosition[1], 0);
+ // We need this compensation to ensure that the QS moves in sync.
+ int clipTopAmountCompensation = 0;
+ if (startClipTopAmount != 0.0f) {
+ clipTopAmountCompensation = (int) MathUtils.lerp(0, startClipTopAmount,
+ Interpolators.FAST_OUT_SLOW_IN.getInterpolation(linearProgress));
+ }
+ return Math.min(top - startPosition[1] - clipTopAmountCompensation, 0);
}
+ public float getProgress() {
+ return linearProgress;
+ }
public float getProgress(long delay, long duration) {
return MathUtils.constrain((linearProgress * ANIMATION_DURATION - delay)
/ duration, 0.0f, 1.0f);
}
+ public int getStartClipTopAmount() {
+ return startClipTopAmount;
+ }
+
+ public int getParentStartClipTopAmount() {
+ return parentStartClipTopAmount;
+ }
+
public float getStartTranslationZ() {
return startTranslationZ;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 79c605e..dc400e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -46,6 +46,7 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.os.Binder;
import android.os.Bundle;
@@ -77,8 +78,8 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
import com.android.systemui.Dependency;
-import com.android.systemui.OverviewProxyService;
import com.android.systemui.Interpolators;
+import com.android.systemui.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.assist.AssistManager;
@@ -111,6 +112,9 @@
private static final boolean DEBUG = false;
private static final String EXTRA_DISABLE_STATE = "disabled_state";
+ private final static int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
+ private final static int ROTATE_BUTTON_LOOP_DURATION_MS = 2000;
+
/** Allow some time inbetween the long press for back and recents. */
private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
@@ -150,8 +154,7 @@
private RotationLockController mRotationLockController;
private TaskStackListenerImpl mTaskStackListener;
- private final Runnable mRemoveRotationProposal = () -> safeSetRotationButtonState(false);
- private Animator mRotateShowAnimator;
+ private final Runnable mRemoveRotationProposal = () -> setRotateSuggestionButtonState(false);
private Animator mRotateHideAnimator;
private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
@@ -364,29 +367,130 @@
// rotate button if shown.
if (!isValid) {
- safeSetRotationButtonState(false);
+ setRotateSuggestionButtonState(false);
return;
}
- if (rotation == mWindowManager.getDefaultDisplay().getRotation()) {
+ final int winRotation = mWindowManager.getDefaultDisplay().getRotation();
+ if (rotation == winRotation) {
// Use this as a signal to remove any current suggestions
getView().getHandler().removeCallbacks(mRemoveRotationProposal);
- safeSetRotationButtonState(false);
+ setRotateSuggestionButtonState(false);
} else {
mLastRotationSuggestion = rotation; // Remember rotation for click
- safeSetRotationButtonState(true);
+
+ // Update the icon style to change animation parameters
+ if (mNavigationBarView != null) {
+ final boolean rotationCCW = isRotationAnimationCCW(winRotation, rotation);
+ int style;
+ if (winRotation == Surface.ROTATION_0 || winRotation == Surface.ROTATION_180) {
+ style = rotationCCW ? R.style.RotateButtonCCWStart90 :
+ R.style.RotateButtonCWStart90;
+ } else { // 90 or 270
+ style = rotationCCW ? R.style.RotateButtonCCWStart0 :
+ R.style.RotateButtonCWStart0;
+ }
+ mNavigationBarView.updateRotateSuggestionButtonStyle(style, true);
+ }
+
+ setRotateSuggestionButtonState(true);
rescheduleRotationTimeout(false);
mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
}
}
- private void safeSetRotationButtonState(boolean vis) {
- if (mNavigationBarView != null) mNavigationBarView.setRotateSuggestionButtonState(vis);
+ private boolean isRotationAnimationCCW(int from, int to) {
+ // All 180deg WM rotation animations are CCW, match that
+ if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
+ if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
+ if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
+ if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
+ if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
+ if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
+ if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
+ if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
+ if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
+ if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
+ if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
+ if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
+ return false; // Default
}
- private void safeSetRotationButtonState(boolean vis, boolean force) {
- if (mNavigationBarView != null) {
- mNavigationBarView.setRotateSuggestionButtonState(vis, force);
+ public void setRotateSuggestionButtonState(final boolean visible) {
+ setRotateSuggestionButtonState(visible, false);
+ }
+
+ public void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
+ if (mNavigationBarView == null) return;
+
+ // At any point the the button can become invisible because an a11y service became active.
+ // Similarly, a call to make the button visible may be rejected because an a11y service is
+ // active. Must account for this.
+
+ ButtonDispatcher rotBtn = mNavigationBarView.getRotateSuggestionButton();
+ final boolean currentlyVisible = mNavigationBarView.isRotateButtonVisible();
+
+ // Rerun a show animation to indicate change but don't rerun a hide animation
+ if (!visible && !currentlyVisible) return;
+
+ View view = rotBtn.getCurrentView();
+ if (view == null) return;
+
+ KeyButtonDrawable kbd = rotBtn.getImageDrawable();
+ if (kbd == null) return;
+
+ // The KBD and AVD is recreated every new valid suggestion because of style changes.
+ AnimatedVectorDrawable animIcon = null;
+ if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) {
+ animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
+ }
+
+ if (visible) { // Appear and change (cannot force)
+ // Stop any currently running hide animations
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.pause();
+ }
+
+ // Reset the alpha if any has changed due to hide animation
+ view.setAlpha(1f);
+
+ // Run the rotate icon's animation if it has one
+ if (animIcon != null) {
+ animIcon.reset();
+ animIcon.start();
+ }
+
+ // Set visibility, may fail if a11y service is active.
+ // If invisible, call will stop animation.
+ mNavigationBarView.setRotateButtonVisibility(true);
+
+ } else { // Hide
+
+ if (force) {
+ // If a hide animator is running stop it and make invisible
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.pause();
+ }
+ mNavigationBarView.setRotateButtonVisibility(false);
+ return;
+ }
+
+ // Don't start any new hide animations if one is running
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+
+ ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha",
+ 0f);
+ fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
+ fadeOut.setInterpolator(Interpolators.LINEAR);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mNavigationBarView.setRotateButtonVisibility(false);
+ }
+ });
+
+ mRotateHideAnimator = fadeOut;
+ fadeOut.start();
}
}
@@ -394,13 +498,9 @@
// May be called due to a new rotation proposal or a change in hover state
if (reasonHover) {
// Don't reschedule if a hide animator is running
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
- return;
- }
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
// Don't reschedule if not visible
- if (mNavigationBarView.getRotateSuggestionButton().getVisibility() != View.VISIBLE) {
- return;
- }
+ if (!mNavigationBarView.isRotateButtonVisible()) return;
}
Handler h = getView().getHandler();
@@ -827,7 +927,7 @@
if (shouldOverrideUserLockPrefs(rotation)) {
mRotationLockController.setRotationLockedAtAngle(true, rotation);
}
- safeSetRotationButtonState(false, true);
+ setRotateSuggestionButtonState(false, true);
}
if (mNavigationBarView != null
@@ -863,22 +963,22 @@
@Override
public void onTaskStackChanged() {
- safeSetRotationButtonState(false);
+ setRotateSuggestionButtonState(false);
}
@Override
public void onTaskRemoved(int taskId) {
- safeSetRotationButtonState(false);
+ setRotateSuggestionButtonState(false);
}
@Override
public void onTaskMovedToFront(int taskId) {
- safeSetRotationButtonState(false);
+ setRotateSuggestionButtonState(false);
}
@Override
public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
- safeSetRotationButtonState(false);
+ setRotateSuggestionButtonState(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 285980b..a5621e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -29,6 +29,7 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.DrawableRes;
+import android.annotation.StyleRes;
import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.Context;
@@ -37,6 +38,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
@@ -87,8 +89,6 @@
final static boolean DEBUG = false;
final static String TAG = "StatusBar/NavBarView";
- final static int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
-
// slippery nav bar when everything is disabled, e.g. during setup
final static boolean SLIPPERY_WHEN_DISABLED = true;
@@ -152,7 +152,7 @@
private RecentsOnboarding mRecentsOnboarding;
private NotificationPanelView mPanelView;
- private Animator mRotateHideAnimator;
+ private int mRotateBtnStyle = R.style.RotateButtonCCWStart90;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -429,10 +429,7 @@
mImeIcon = getDrawable(darkContext, lightContext,
R.drawable.ic_ime_switcher_default, R.drawable.ic_ime_switcher_default);
- int lightColor = Utils.getColorAttr(lightContext, R.attr.singleToneColor);
- int darkColor = Utils.getColorAttr(darkContext, R.attr.singleToneColor);
- mRotateSuggestionIcon = getDrawable(ctx, R.drawable.ic_sysbar_rotate_button,
- lightColor, darkColor);
+ updateRotateSuggestionButtonStyle(mRotateBtnStyle, false);
if (ALTERNATE_CAR_MODE_UI) {
updateCarModeIcons(ctx);
@@ -726,93 +723,61 @@
// Accessibility button overrides Menu, IME switcher and rotate buttons.
setMenuVisibility(false, true);
getImeSwitchButton().setVisibility(View.INVISIBLE);
- setRotateSuggestionButtonState(false, true);
+ setRotateButtonVisibility(false);
}
getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
getAccessibilityButton().setLongClickable(longClickable);
}
- public void setRotateSuggestionButtonState(final boolean visible) {
- setRotateSuggestionButtonState(visible, false);
+ public void updateRotateSuggestionButtonStyle(@StyleRes int style, boolean setIcon) {
+ mRotateBtnStyle = style;
+ final Context ctx = getContext();
+
+ // Extract the dark and light tints
+ final int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
+ final int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
+ Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
+ Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
+ final int lightColor = Utils.getColorAttr(lightContext, R.attr.singleToneColor);
+ final int darkColor = Utils.getColorAttr(darkContext, R.attr.singleToneColor);
+
+ // Use the supplied style to set the icon's rotation parameters
+ Context rotateContext = new ContextThemeWrapper(ctx, style);
+
+ // Recreate the icon and set it if needed
+ mRotateSuggestionIcon = getDrawable(rotateContext, R.drawable.ic_sysbar_rotate_button,
+ lightColor, darkColor);
+ if (setIcon) getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
}
- public void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
- ButtonDispatcher rotBtn = getRotateSuggestionButton();
- final boolean currentlyVisible = mShowRotateButton;
-
- // Rerun a show animation to indicate change but don't rerun a hide animation
- if (!visible && !currentlyVisible) return;
-
- View currentView = rotBtn.getCurrentView();
- if (currentView == null) return;
-
- KeyButtonDrawable kbd = rotBtn.getImageDrawable();
- if (kbd == null) return;
-
- AnimatedVectorDrawable animIcon = null;
- if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) {
- animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
- }
-
- if (visible) { // Appear and change, cannot force
- setRotateButtonVisibility(true);
-
- // Stop any currently running hide animations
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
- mRotateHideAnimator.pause();
- }
-
- // Reset the alpha if any has changed due to hide animation
- currentView.setAlpha(1f);
-
- // Run the rotate icon's animation if it has one
- if (animIcon != null) {
- animIcon.reset();
- animIcon.start();
- }
-
- } else { // Hide
- if (force) {
- // If a hide animator is running stop it and instantly make invisible
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
- mRotateHideAnimator.pause();
- }
- setRotateButtonVisibility(false);
- return;
- }
-
- // Don't start any new hide animations if one is running
- if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
-
- ObjectAnimator fadeOut = ObjectAnimator.ofFloat(currentView, "alpha",
- 0f);
- fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
- fadeOut.setInterpolator(Interpolators.LINEAR);
- fadeOut.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setRotateButtonVisibility(false);
- }
- });
-
- mRotateHideAnimator = fadeOut;
- fadeOut.start();
- }
- }
-
- private void setRotateButtonVisibility(final boolean visible) {
+ public void setRotateButtonVisibility(final boolean visible) {
// Never show if a11y is visible
final boolean adjVisible = visible && !mShowAccessibilityButton;
final int vis = adjVisible ? View.VISIBLE : View.INVISIBLE;
+ // No need to do anything if the request matches the current state
+ if (vis == getRotateSuggestionButton().getVisibility()) return;
+
getRotateSuggestionButton().setVisibility(vis);
mShowRotateButton = visible;
+ // Stop any active animations if hidden
+ if (!visible) {
+ Drawable d = mRotateSuggestionIcon.getDrawable(0);
+ if (d instanceof AnimatedVectorDrawable) {
+ AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d;
+ avd.clearAnimationCallbacks();
+ avd.reset();
+ }
+ }
+
// Hide/restore other button visibility, if necessary
setNavigationIconHints(mNavigationIconHints, true);
}
+ public boolean isRotateButtonVisible() { return mShowRotateButton; }
+
@Override
public void onFinishInflate() {
mNavigationInflaterView = (NavigationBarInflaterView) findViewById(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 40fe50f..e77b135 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -140,6 +140,11 @@
return false;
}
+ // showAmbient == show in shade but not shelf
+ if (!showAmbient && notificationData.shouldSuppressScreenOn(entry.key)) {
+ return false;
+ }
+
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 2b50853..255e5e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -112,7 +112,6 @@
protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
static final int TAG_KEY_ANIM = R.id.scrim;
- private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
private static final float NOT_INITIALIZED = -1;
@@ -138,7 +137,8 @@
protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
- private float mFraction;
+ // Assuming the shade is expanded during initialization
+ private float mExpansionFraction = 1f;
private boolean mDarkenWhileDragging;
protected boolean mAnimateChange;
@@ -252,6 +252,7 @@
mCurrentBehindTint = state.getBehindTint();
mCurrentInFrontAlpha = state.getFrontAlpha();
mCurrentBehindAlpha = state.getBehindAlpha();
+ applyExpansionToAlpha();
// Cancel blanking transitions that were pending before we requested a new state
if (mPendingFrameCallback != null) {
@@ -363,45 +364,50 @@
* @param fraction From 0 to 1 where 0 means collapse and 1 expanded.
*/
public void setPanelExpansion(float fraction) {
- if (mFraction != fraction) {
- mFraction = fraction;
+ if (mExpansionFraction != fraction) {
+ mExpansionFraction = fraction;
- if (mState == ScrimState.UNLOCKED) {
- // Darken scrim as you pull down the shade when unlocked
- float behindFraction = getInterpolatedFraction();
- behindFraction = (float) Math.pow(behindFraction, 0.8f);
- mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
- mCurrentInFrontAlpha = 0;
- } else if (mState == ScrimState.KEYGUARD) {
- if (mUpdatePending) {
- return;
- }
+ if (!(mState == ScrimState.UNLOCKED || mState == ScrimState.KEYGUARD)) {
+ return;
+ }
- // Either darken of make the scrim transparent when you
- // pull down the shade
- float interpolatedFract = getInterpolatedFraction();
- if (mDarkenWhileDragging) {
- mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
- mScrimBehindAlphaKeyguard, interpolatedFract);
- mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
- } else {
- mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
- interpolatedFract);
- mCurrentInFrontAlpha = 0;
- }
- } else {
+ applyExpansionToAlpha();
+
+ if (mUpdatePending) {
return;
}
if (mPinnedHeadsUpCount != 0) {
updateHeadsUpScrim(false);
}
-
updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha);
updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha);
}
}
+ private void applyExpansionToAlpha() {
+ if (mState == ScrimState.UNLOCKED) {
+ // Darken scrim as you pull down the shade when unlocked
+ float behindFraction = getInterpolatedFraction();
+ behindFraction = (float) Math.pow(behindFraction, 0.8f);
+ mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ } else if (mState == ScrimState.KEYGUARD) {
+ // Either darken of make the scrim transparent when you
+ // pull down the shade
+ float interpolatedFract = getInterpolatedFraction();
+ if (mDarkenWhileDragging) {
+ mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
+ mScrimBehindAlphaKeyguard, interpolatedFract);
+ mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
+ } else {
+ mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+ interpolatedFract);
+ mCurrentInFrontAlpha = 0;
+ }
+ }
+ }
+
/**
* Keyguard and shade scrim opacity varies according to how many notifications are visible.
* @param notificationCount Number of visible notifications.
@@ -497,7 +503,7 @@
}
private float getInterpolatedFraction() {
- float frac = mFraction;
+ float frac = mExpansionFraction;
// let's start this 20% of the way down the screen
frac = frac * 1.2f - 0.2f;
if (frac <= 0) {
@@ -551,7 +557,7 @@
return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint;
}
- private void startScrimAnimation(final View scrim, float current, float target) {
+ private void startScrimAnimation(final View scrim, float current) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
Color.TRANSPARENT;
@@ -559,7 +565,9 @@
final float animAmount = (float) animation.getAnimatedValue();
final int finalScrimTint = scrim == mScrimInFront ?
mCurrentInFrontTint : mCurrentBehindTint;
- float alpha = MathUtils.lerp(current, target, animAmount);
+ float finalScrimAlpha = scrim == mScrimInFront ?
+ mCurrentInFrontAlpha : mCurrentBehindAlpha;
+ float alpha = MathUtils.lerp(current, finalScrimAlpha, animAmount);
int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
updateScrimColor(scrim, alpha, tint);
dispatchScrimsVisible();
@@ -570,6 +578,12 @@
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ final int finalScrimTint = scrim == mScrimInFront ?
+ mCurrentInFrontTint : mCurrentBehindTint;
+ float finalScrimAlpha = scrim == mScrimInFront ?
+ mCurrentInFrontAlpha : mCurrentBehindAlpha;
+ updateScrimColor(scrim, finalScrimAlpha, finalScrimTint);
+
if (mKeyguardFadingOutInProgress) {
mKeyguardFadeoutAnimation = null;
mKeyguardFadingOutInProgress = false;
@@ -577,7 +591,6 @@
onFinished();
scrim.setTag(TAG_KEY_ANIM, null);
- scrim.setTag(TAG_KEY_ANIM_TARGET, null);
dispatchScrimsVisible();
if (!mDeferFinishedListener && mOnAnimationFinished != null) {
@@ -592,7 +605,6 @@
mKeyguardFadeoutAnimation = anim;
}
scrim.setTag(TAG_KEY_ANIM, anim);
- scrim.setTag(TAG_KEY_ANIM_TARGET, target);
}
protected Interpolator getInterpolator() {
@@ -700,7 +712,7 @@
if (animate) {
mDeferFinishedListener = true;
}
- previousAnimator.cancel();
+ cancelAnimator(previousAnimator);
mDeferFinishedListener = false;
} else {
animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
@@ -709,9 +721,11 @@
if (mPendingFrameCallback != null) {
// Display is off and we're waiting.
+ cancelAnimator(previousAnimator);
return;
} else if (mBlankScreen) {
// Need to blank the display before continuing.
+ cancelAnimator(previousAnimator);
blankDisplay();
return;
} else if (!mScreenBlankingCallbackCalled) {
@@ -737,7 +751,7 @@
if (animate) {
final float fromAlpha = scrimView == null ? scrim.getAlpha()
: scrimView.getViewAlpha();
- startScrimAnimation(scrim, fromAlpha, alpha);
+ startScrimAnimation(scrim, fromAlpha);
scrim.setTag(TAG_START_ALPHA, currentAlpha);
scrim.setTag(TAG_END_ALPHA, alpha);
} else {
@@ -765,6 +779,13 @@
}
}
+ @VisibleForTesting
+ protected void cancelAnimator(ValueAnimator previousAnimator) {
+ if (previousAnimator != null) {
+ previousAnimator.cancel();
+ }
+ }
+
private void blankDisplay() {
updateScrimColor(mScrimInFront, 1, Color.BLACK);
@@ -827,7 +848,7 @@
} else {
alpha = 1.0f - mTopHeadsUpDragAmount;
}
- float expandFactor = (1.0f - mFraction);
+ float expandFactor = (1.0f - mExpansionFraction);
expandFactor = Math.max(expandFactor, 0.0f);
return alpha * expandFactor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 24920cb..4ffe5fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2804,6 +2804,7 @@
public void setRemoteInputActive(NotificationData.Entry entry,
boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+ entry.row.updateMaxHeights();
}
public void lockScrollTo(NotificationData.Entry entry) {
mStackScroller.lockScrollTo(entry.row);
@@ -4519,6 +4520,9 @@
}
if (isScreenTurningOnOrOn()) {
if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
+ if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ mStatusBarKeyguardViewManager.hideBouncer(false /* destroyView */);
+ }
mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
updateScrimController();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index c667309..47ea3a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -161,7 +161,7 @@
updateStates();
}
- private void hideBouncer(boolean destroyView) {
+ public void hideBouncer(boolean destroyView) {
mBouncer.hide(destroyView);
cancelPendingWakeupAction();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index 0d21c4e..6ee6cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -102,8 +102,6 @@
@Override
public void scanForAccessPoints() {
- if (DEBUG) Log.d(TAG, "force update APs!");
- mWifiTracker.forceUpdate();
fireAcccessPointsCallback(mWifiTracker.getAccessPoints());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 179c0d5..d74a59e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -60,6 +60,8 @@
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.stack.StackStateAnimator;
+import java.util.function.Consumer;
+
/**
* Host for the remote input.
*/
@@ -90,6 +92,7 @@
private boolean mResetting;
private NotificationViewWrapper mWrapper;
+ private Consumer<Boolean> mOnVisibilityChangedListener;
public RemoteInputView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -451,6 +454,18 @@
mWrapper = wrapper;
}
+ public void setOnVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener) {
+ mOnVisibilityChangedListener = visibilityChangedListener;
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (changedView == this) {
+ mOnVisibilityChangedListener.accept(visibility == VISIBLE);
+ }
+ }
+
/**
* An EditText that changes appearance based on whether it's focusable and becomes
* un-focusable whenever the user navigates away from it or it becomes invisible.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index d7a810e..0f637fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -220,8 +220,7 @@
}
public int getInnerHeight() {
- return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding
- - mExpandAnimationTopChange, mLayoutMinHeight);
+ return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding, mLayoutMinHeight);
}
public boolean isShadeExpanded() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 4ca33cd..ac2a1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -120,6 +120,7 @@
super(context, attrs, defStyleAttr, defStyleRes);
initDimens();
mHybridGroupManager = new HybridGroupManager(getContext(), this);
+ setClipChildren(false);
}
private void initDimens() {
@@ -134,7 +135,7 @@
R.dimen.notification_children_container_top_padding);
mHeaderHeight = mNotificationHeaderMargin + mNotificatonTopPadding;
mCollapsedBottompadding = res.getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_bottom);
+ com.android.internal.R.dimen.notification_content_margin);
mEnableShadowOnChildNotifications =
res.getBoolean(R.bool.config_enableShadowOnChildNotifications);
mShowDividersWhenExpanded =
@@ -533,11 +534,12 @@
/**
* Update the state of all its children based on a linear layout algorithm.
- *
- * @param resultState the state to update
+ * @param resultState the state to update
* @param parentState the state of the parent
+ * @param ambientState
*/
- public void getState(StackScrollState resultState, ExpandableViewState parentState) {
+ public void getState(StackScrollState resultState, ExpandableViewState parentState,
+ AmbientState ambientState) {
int childCount = mChildren.size();
int yPosition = mNotificationHeaderMargin;
boolean firstChild = true;
@@ -553,6 +555,7 @@
boolean childrenExpandedAndNotAnimating = mChildrenExpanded
&& !mContainingNotification.isGroupExpansionChanging();
+ int launchTransitionCompensation = 0;
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
if (!firstChild) {
@@ -577,13 +580,13 @@
ExpandableViewState childState = resultState.getViewStateForView(child);
int intrinsicHeight = child.getIntrinsicHeight();
childState.height = intrinsicHeight;
- childState.yTranslation = yPosition;
+ childState.yTranslation = yPosition + launchTransitionCompensation;
childState.hidden = false;
// When the group is expanded, the children cast the shadows rather than the parent
// so use the parent's elevation here.
childState.zTranslation =
(childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications)
- ? mContainingNotification.getTranslationZ()
+ ? parentState.zTranslation
: 0;
childState.dimmed = parentState.dimmed;
childState.dark = parentState.dark;
@@ -600,6 +603,9 @@
childState.location = parentState.location;
childState.inShelf = parentState.inShelf;
yPosition += intrinsicHeight;
+ if (child.isExpandAnimationRunning()) {
+ launchTransitionCompensation = -ambientState.getExpandAnimationTopChange();
+ }
}
if (mOverflowNumber != null) {
@@ -637,7 +643,7 @@
}
mHeaderViewState.initFrom(mNotificationHeader);
mHeaderViewState.zTranslation = childrenExpandedAndNotAnimating
- ? mContainingNotification.getTranslationZ()
+ ? parentState.zTranslation
: 0;
}
}
@@ -727,6 +733,9 @@
}
private void updateChildrenClipping() {
+ if (mContainingNotification.hasExpandingChild()) {
+ return;
+ }
int childCount = mChildren.size();
int layoutEnd = mContainingNotification.getActualHeight() - mClipBottomAmount;
for (int i = 0; i < childCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index d68a7b1..51737a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -91,17 +91,18 @@
updateClipping(resultState, algorithmState, ambientState);
updateSpeedBumpState(resultState, algorithmState, ambientState);
updateShelfState(resultState, ambientState);
- getNotificationChildrenStates(resultState, algorithmState);
+ getNotificationChildrenStates(resultState, algorithmState, ambientState);
}
private void getNotificationChildrenStates(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView v = algorithmState.visibleChildren.get(i);
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- row.getChildrenStates(resultState);
+ row.getChildrenStates(resultState, ambientState);
}
}
}
@@ -323,7 +324,9 @@
}
ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
state.indexOfExpandingNotification = expandingNotification != null
- ? state.visibleChildren.indexOf(expandingNotification)
+ ? expandingNotification.isChildInGroup()
+ ? state.visibleChildren.indexOf(expandingNotification.getNotificationParent())
+ : state.visibleChildren.indexOf(expandingNotification)
: -1;
}
@@ -386,7 +389,7 @@
childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
float inset = ambientState.getTopPadding() + ambientState.getStackTranslation();
- if (i < algorithmState.getIndexOfExpandingNotification()) {
+ if (i <= algorithmState.getIndexOfExpandingNotification()) {
inset += ambientState.getExpandAnimationTopChange();
}
if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
@@ -515,7 +518,7 @@
- ambientState.getShelf().getIntrinsicHeight();
childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
if (childViewState.yTranslation >= shelfStart) {
- childViewState.hidden = !child.isExpandAnimationRunning();
+ childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild();
childViewState.inShelf = true;
childViewState.headsUpIsVisible = false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 1c9c794..6764634 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -37,6 +37,7 @@
import android.content.Intent;
import android.metrics.LogMaker;
import android.support.test.filters.SmallTest;
+import android.support.test.InstrumentationRegistry;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -73,6 +74,7 @@
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mHost = mock(QSTileHost.class);
when(mHost.indexOf(spec)).thenReturn(POSITION);
+ when(mHost.getContext()).thenReturn(mContext.getBaseContext());
mTile = spy(new TileImpl(mHost));
mTile.mHandler = mTile.new H(mTestableLooper.getLooper());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 43e16db..8347fb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -31,6 +31,7 @@
import static org.mockito.Mockito.when;
import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.app.AlarmManager;
import android.graphics.Color;
import android.os.Handler;
@@ -180,6 +181,7 @@
@Test
public void transitionToUnlocked() {
+ mScrimController.setPanelExpansion(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
@@ -197,6 +199,7 @@
public void transitionToUnlockedFromAod() {
// Simulate unlock with fingerprint
mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.setPanelExpansion(0f);
mScrimController.finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
// Immediately tinted after the transition starts
@@ -324,6 +327,35 @@
verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
}
+ @Test
+ public void testConservesExpansionOpacityAfterTransition() {
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.setPanelExpansion(0.5f);
+ mScrimController.finishAnimationsImmediately();
+
+ final float expandedAlpha = mScrimBehind.getViewAlpha();
+
+ mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ mScrimController.finishAnimationsImmediately();
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.finishAnimationsImmediately();
+
+ Assert.assertEquals("Scrim expansion opacity wasn't conserved when transitioning back",
+ expandedAlpha, mScrimBehind.getViewAlpha(), 0.01f);
+ }
+
+ @Test
+ public void cancelsOldAnimationBeforeBlanking() {
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ // Consume whatever value we had before
+ mScrimController.wasAnimationJustCancelled();
+
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.finishAnimationsImmediately();
+ Assert.assertTrue(mScrimController.wasAnimationJustCancelled());
+ }
+
private void assertScrimTint(ScrimView scrimView, boolean tinted) {
final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
final String name = scrimView == mScrimInFront ? "front" : "back";
@@ -357,6 +389,7 @@
private class SynchronousScrimController extends ScrimController {
private FakeHandler mHandler;
+ private boolean mAnimationCancelled;
public SynchronousScrimController(LightBarController lightBarController,
ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
@@ -385,6 +418,12 @@
}
}
+ public boolean wasAnimationJustCancelled() {
+ final boolean wasCancelled = mAnimationCancelled;
+ mAnimationCancelled = false;
+ return wasCancelled;
+ }
+
private void endAnimation(ScrimView scrimView, int tag) {
Animator animator = (Animator) scrimView.getTag(tag);
if (animator != null) {
@@ -393,6 +432,12 @@
}
@Override
+ protected void cancelAnimator(ValueAnimator previousAnimator) {
+ super.cancelAnimator(previousAnimator);
+ mAnimationCancelled = true;
+ }
+
+ @Override
protected Handler getHandler() {
return mHandler;
}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 7e86ef5..c0e5960 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5283,6 +5283,40 @@
// OS: P
USB_DEFAULT = 1312;
+ // CATEGORY: The category for all actions related to TextClassifier generateLinks.
+ // OS: P
+ TEXT_CLASSIFIER_GENERATE_LINKS = 1313;
+
+ // FIELD: milliseconds spent generating links.
+ // CATEGORY: TEXT_CLASSIFIER_GENERATE_LINKS
+ // OS: P
+ FIELD_LINKIFY_LATENCY = 1314;
+
+ // FIELD: length of the input text in characters.
+ // CATEGORY: TEXT_CLASSIFIER_GENERATE_LINKS
+ // OS: P
+ FIELD_LINKIFY_TEXT_LENGTH = 1315;
+
+ // FIELD: number of links detected.
+ // CATEGORY: TEXT_CLASSIFIER_GENERATE_LINKS
+ // OS: P
+ FIELD_LINKIFY_NUM_LINKS = 1316;
+
+ // FIELD: length of all links in characters.
+ // CATEGORY: TEXT_CLASSIFIER_GENERATE_LINKS
+ // OS: P
+ FIELD_LINKIFY_LINK_LENGTH = 1317;
+
+ // FIELD: the type of entity the stats are for.
+ // CATEGORY: TEXT_CLASSIFIER_GENERATE_LINKS
+ // OS: P
+ FIELD_LINKIFY_ENTITY_TYPE = 1318;
+
+ // FIELD: a random uid for a single call to generateLinks
+ // CATEGORY: TEXT_CLASSIFIER_GENERATE_LINKS
+ // OS: P
+ FIELD_LINKIFY_CALL_ID = 1319;
+
// ---- End P Constants, all P constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index c201de4..8622dbe 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -41,6 +41,7 @@
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteCallbackList;
@@ -76,7 +77,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.os.HandlerCaller;
import com.android.server.LocalServices;
import com.android.server.autofill.ui.AutoFillUI;
@@ -99,8 +99,6 @@
/** Minimum interval to prune abandoned sessions */
private static final int MAX_ABANDONED_SESSION_MILLIS = 30000;
- static final int MSG_SERVICE_SAVE = 1;
-
private final int mUserId;
private final Context mContext;
private final Object mLock;
@@ -151,18 +149,7 @@
@GuardedBy("mLock")
private boolean mSetupComplete;
- private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
- switch (msg.what) {
- case MSG_SERVICE_SAVE:
- handleSessionSave(msg.arg1);
- break;
- default:
- Slog.w(TAG, "invalid msg on handler: " + msg);
- }
- };
-
- private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
- mHandlerCallback, true);
+ private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
/**
* Cache of pending {@link Session}s, keyed by sessionId.
@@ -508,7 +495,7 @@
assertCallerLocked(componentName);
- final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
+ final Session newSession = new Session(this, mUi, mContext, mHandler, mUserId, mLock,
sessionId, uid, activityToken, appCallbackToken, hasCallback, mUiLatencyHistory,
mWtfHistory, mInfo.getServiceInfo().getComponentName(), componentName, compatMode,
flags);
@@ -597,11 +584,10 @@
mSessions.remove(sessionId);
}
- private void handleSessionSave(int sessionId) {
+ void handleSessionSave(Session session) {
synchronized (mLock) {
- final Session session = mSessions.get(sessionId);
- if (session == null) {
- Slog.w(TAG, "handleSessionSave(): already gone: " + sessionId);
+ if (mSessions.get(session.id) == null) {
+ Slog.w(TAG, "handleSessionSave(): already gone: " + session.id);
return;
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index fe6d4c4..d4ecc28 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -18,6 +18,7 @@
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
@@ -32,7 +33,6 @@
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.ICancellationSignal;
-import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -47,7 +47,6 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.HandlerCaller;
import com.android.server.FgThread;
import java.io.PrintWriter;
@@ -63,13 +62,14 @@
*/
final class RemoteFillService implements DeathRecipient {
private static final String LOG_TAG = "RemoteFillService";
-
// How long after the last interaction with the service we would unbind
private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
// How long after we make a remote request to a fill service we timeout
private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
+ private static final int MSG_UNBIND = 3;
+
private final Context mContext;
private final ComponentName mComponentName;
@@ -82,7 +82,7 @@
private final ServiceConnection mServiceConnection = new RemoteServiceConnection();
- private final HandlerCaller mHandler;
+ private final Handler mHandler;
private IAutoFillService mAutoFillService;
@@ -115,14 +115,16 @@
mComponentName = componentName;
mIntent = new Intent(AutofillService.SERVICE_INTERFACE).setComponent(mComponentName);
mUserId = userId;
- mHandler = new MyHandler(context);
+ mHandler = new Handler(FgThread.getHandler().getLooper());
}
public void destroy() {
- mHandler.obtainMessage(MyHandler.MSG_DESTROY).sendToTarget();
+ mHandler.sendMessage(obtainMessage(
+ RemoteFillService::handleDestroy, this));
}
private void handleDestroy() {
+ if (checkIfDestroyed()) return;
if (mPendingRequest != null) {
mPendingRequest.cancel();
mPendingRequest = null;
@@ -133,10 +135,12 @@
@Override
public void binderDied() {
- mHandler.obtainMessage(MyHandler.MSG_BINDER_DIED).sendToTarget();
+ mHandler.sendMessage(obtainMessage(
+ RemoteFillService::handleBinderDied, this));
}
private void handleBinderDied() {
+ if (checkIfDestroyed()) return;
if (mAutoFillService != null) {
mAutoFillService.asBinder().unlinkToDeath(this, 0);
}
@@ -174,14 +178,17 @@
public void onFillRequest(@NonNull FillRequest request) {
cancelScheduledUnbind();
- final PendingFillRequest pendingRequest = new PendingFillRequest(request, this);
- mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, pendingRequest).sendToTarget();
+ scheduleRequest(new PendingFillRequest(request, this));
}
public void onSaveRequest(@NonNull SaveRequest request) {
cancelScheduledUnbind();
- final PendingSaveRequest pendingRequest = new PendingSaveRequest(request, this);
- mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, pendingRequest).sendToTarget();
+ scheduleRequest(new PendingSaveRequest(request, this));
+ }
+
+ private void scheduleRequest(PendingRequest pendingRequest) {
+ mHandler.sendMessage(obtainMessage(
+ RemoteFillService::handlePendingRequest, this, pendingRequest));
}
// Note: we are dumping without a lock held so this is a bit racy but
@@ -204,21 +211,25 @@
}
private void cancelScheduledUnbind() {
- mHandler.removeMessages(MyHandler.MSG_UNBIND);
+ mHandler.removeMessages(MSG_UNBIND);
}
private void scheduleUnbind() {
cancelScheduledUnbind();
- Message message = mHandler.obtainMessage(MyHandler.MSG_UNBIND);
- mHandler.sendMessageDelayed(message, TIMEOUT_IDLE_BIND_MILLIS);
+ mHandler.sendMessageDelayed(
+ obtainMessage(RemoteFillService::handleUnbind, this)
+ .setWhat(MSG_UNBIND),
+ TIMEOUT_IDLE_BIND_MILLIS);
}
private void handleUnbind() {
+ if (checkIfDestroyed()) return;
ensureUnbound();
}
private void handlePendingRequest(PendingRequest pendingRequest) {
- if (mDestroyed || mCompleted) {
+ if (checkIfDestroyed()) return;
+ if (mCompleted) {
return;
}
if (!isBound()) {
@@ -283,7 +294,7 @@
private void dispatchOnFillRequestSuccess(PendingRequest pendingRequest, int requestFlags,
FillResponse response) {
- mHandler.getHandler().post(() -> {
+ mHandler.post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onFillRequestSuccess(requestFlags, response,
mComponentName.getPackageName());
@@ -293,7 +304,7 @@
private void dispatchOnFillRequestFailure(PendingRequest pendingRequest,
@Nullable CharSequence message) {
- mHandler.getHandler().post(() -> {
+ mHandler.post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onFillRequestFailure(message, mComponentName.getPackageName());
}
@@ -301,7 +312,7 @@
}
private void dispatchOnFillTimeout(@NonNull ICancellationSignal cancellationSignal) {
- mHandler.getHandler().post(() -> {
+ mHandler.post(() -> {
try {
cancellationSignal.cancel();
} catch (RemoteException e) {
@@ -312,7 +323,7 @@
private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest,
IntentSender intentSender) {
- mHandler.getHandler().post(() -> {
+ mHandler.post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), intentSender);
}
@@ -321,7 +332,7 @@
private void dispatchOnSaveRequestFailure(PendingRequest pendingRequest,
@Nullable CharSequence message) {
- mHandler.getHandler().post(() -> {
+ mHandler.post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onSaveRequestFailure(message, mComponentName.getPackageName());
}
@@ -378,44 +389,14 @@
}
}
- private final class MyHandler extends HandlerCaller {
- public static final int MSG_DESTROY = 1;
- public static final int MSG_BINDER_DIED = 2;
- public static final int MSG_UNBIND = 3;
- public static final int MSG_ON_PENDING_REQUEST = 4;
-
- public MyHandler(Context context) {
- // Cannot use lambda - doesn't compile
- super(context, FgThread.getHandler().getLooper(), new Callback() {
- @Override
- public void executeMessage(Message message) {
- if (mDestroyed) {
- if (sVerbose) {
- Slog.v(LOG_TAG, "Not handling " + message + " as service for "
- + mComponentName + " is already destroyed");
- }
- return;
- }
- switch (message.what) {
- case MSG_DESTROY: {
- handleDestroy();
- } break;
-
- case MSG_BINDER_DIED: {
- handleBinderDied();
- } break;
-
- case MSG_UNBIND: {
- handleUnbind();
- } break;
-
- case MSG_ON_PENDING_REQUEST: {
- handlePendingRequest((PendingRequest) message.obj);
- } break;
- }
- }
- }, false);
+ private boolean checkIfDestroyed() {
+ if (mDestroyed) {
+ if (sVerbose) {
+ Slog.v(LOG_TAG, "Not handling operation as service for "
+ + mComponentName + " is already destroyed");
+ }
}
+ return mDestroyed;
}
private static abstract class PendingRequest implements Runnable {
@@ -433,7 +414,7 @@
PendingRequest(RemoteFillService service) {
mWeakService = new WeakReference<>(service);
- mServiceHandler = service.mHandler.getHandler();
+ mServiceHandler = service.mHandler;
mTimeoutTrigger = () -> {
synchronized (mLock) {
if (mCancelled) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ef6ed08..32f03b3 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -26,6 +26,7 @@
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sPartitionMaxCount;
import static com.android.server.autofill.Helper.sVerbose;
@@ -49,6 +50,7 @@
import android.metrics.LogMaker;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteCallback;
@@ -117,7 +119,7 @@
private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
private final AutofillManagerServiceImpl mService;
- private final HandlerCaller mHandlerCaller;
+ private final Handler mHandler;
private final Object mLock;
private final AutoFillUI mUi;
@@ -485,7 +487,7 @@
}
Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
- @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
+ @NonNull Context context, @NonNull Handler handler, int userId,
@NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
@NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
@NonNull LocalLog wtfHistory,
@@ -498,7 +500,7 @@
mService = service;
mLock = lock;
mUi = ui;
- mHandlerCaller = handlerCaller;
+ mHandler = handler;
mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this);
mActivityToken = activityToken;
mHasCallback = hasCallback;
@@ -726,8 +728,9 @@
mService.setAuthenticationSelected(id, mClientState);
final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
- mHandlerCaller.getHandler().post(() -> startAuthentication(authenticationId,
- intent, fillInIntent));
+ mHandler.sendMessage(obtainMessage(
+ Session::startAuthentication,
+ this, authenticationId, intent, fillInIntent));
}
// FillServiceCallbacks
@@ -746,7 +749,9 @@
return;
}
}
- mHandlerCaller.getHandler().post(() -> autoFill(requestId, datasetIndex, dataset, true));
+ mHandler.sendMessage(obtainMessage(
+ Session::autoFill,
+ this, requestId, datasetIndex, dataset, true));
}
// AutoFillUiCallback
@@ -759,9 +764,9 @@
return;
}
}
- mHandlerCaller.getHandler()
- .obtainMessage(AutofillManagerServiceImpl.MSG_SERVICE_SAVE, id, 0)
- .sendToTarget();
+ mHandler.sendMessage(obtainMessage(
+ AutofillManagerServiceImpl::handleSessionSave,
+ mService, this));
}
// AutoFillUiCallback
@@ -776,7 +781,8 @@
return;
}
}
- mHandlerCaller.getHandler().post(() -> removeSelf());
+ mHandler.sendMessage(obtainMessage(
+ Session::removeSelf, this));
}
// AutoFillUiCallback
@@ -831,15 +837,19 @@
}
removeSelfLocked();
}
- mHandlerCaller.getHandler().post(() -> {
- try {
- synchronized (mLock) {
- mClient.startIntentSender(intentSender, null);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Error launching auth intent", e);
+ mHandler.sendMessage(obtainMessage(
+ Session::doStartIntentSender,
+ this, intentSender));
+ }
+
+ private void doStartIntentSender(IntentSender intentSender) {
+ try {
+ synchronized (mLock) {
+ mClient.startIntentSender(intentSender, null);
}
- });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error launching auth intent", e);
+ }
}
@GuardedBy("mLock")
@@ -960,11 +970,14 @@
* when necessary.
*/
public void logContextCommitted() {
- mHandlerCaller.getHandler().post(() -> {
- synchronized (mLock) {
- logContextCommittedLocked();
- }
- });
+ mHandler.sendMessage(obtainMessage(
+ Session::doLogContextCommitted, this));
+ }
+
+ private void doLogContextCommitted() {
+ synchronized (mLock) {
+ logContextCommittedLocked();
+ }
}
@GuardedBy("mLock")
@@ -1486,7 +1499,8 @@
}
// Use handler so logContextCommitted() is logged first
- mHandlerCaller.getHandler().post(() -> mService.logSaveShown(id, mClientState));
+ mHandler.sendMessage(obtainMessage(
+ Session::logSaveShown, this));
final IAutoFillManagerClient client = getClient();
mPendingSaveUi = new PendingUi(mActivityToken, id, client);
@@ -1514,6 +1528,10 @@
return true;
}
+ private void logSaveShown() {
+ mService.logSaveShown(id, mClientState);
+ }
+
@Nullable
private ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) {
if (saveInfo == null) return null;
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 1d5e47a..c570005 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1706,6 +1706,13 @@
if (cs != null) {
clearClientSessionLocked(cs);
if (mCurClient == cs) {
+ if (mBoundToMethod) {
+ mBoundToMethod = false;
+ if (mCurMethod != null) {
+ executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
+ MSG_UNBIND_INPUT, mCurMethod));
+ }
+ }
mCurClient = null;
}
if (mCurFocusedWindowClient == cs) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 5e5eacb..ce78665 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -35,6 +35,7 @@
import android.telephony.CellInfo;
import android.telephony.CellLocation;
import android.telephony.DisconnectCause;
+import android.telephony.LocationAccessPolicy;
import android.telephony.PhoneStateListener;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
@@ -96,7 +97,8 @@
IPhoneStateListener callback;
IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
- int callerUserId;
+ int callerUid;
+ int callerPid;
int events;
@@ -120,7 +122,7 @@
+ " callback=" + callback
+ " onSubscriptionsChangedListenererCallback="
+ onSubscriptionsChangedListenerCallback
- + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId
+ + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId
+ " events=" + Integer.toHexString(events)
+ " canReadPhoneState=" + canReadPhoneState + "}";
}
@@ -374,6 +376,8 @@
public void addOnSubscriptionsChangedListener(String callingPackage,
IOnSubscriptionsChangedListener callback) {
int callerUserId = UserHandle.getCallingUserId();
+ mContext.getSystemService(AppOpsManager.class)
+ .checkPackage(Binder.getCallingUid(), callingPackage);
if (VDBG) {
log("listen oscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId()
+ " callerUserId=" + callerUserId + " callback=" + callback
@@ -408,7 +412,8 @@
r.onSubscriptionsChangedListenerCallback = callback;
r.callingPackage = callingPackage;
- r.callerUserId = callerUserId;
+ r.callerUid = Binder.getCallingUid();
+ r.callerPid = Binder.getCallingPid();
r.events = 0;
r.canReadPhoneState = true; // permission has been enforced above
if (DBG) {
@@ -479,6 +484,8 @@
private void listen(String callingPackage, IPhoneStateListener callback, int events,
boolean notifyNow, int subId) {
int callerUserId = UserHandle.getCallingUserId();
+ mContext.getSystemService(AppOpsManager.class)
+ .checkPackage(Binder.getCallingUid(), callingPackage);
if (VDBG) {
log("listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events)
+ " notifyNow=" + notifyNow + " subId=" + subId + " myUserId="
@@ -503,6 +510,7 @@
}
}
+ int phoneId = SubscriptionManager.getPhoneId(subId);
synchronized (mRecords) {
// register
IBinder b = callback.asBinder();
@@ -514,7 +522,8 @@
r.callback = callback;
r.callingPackage = callingPackage;
- r.callerUserId = callerUserId;
+ r.callerUid = Binder.getCallingUid();
+ r.callerPid = Binder.getCallingPid();
boolean isPhoneStateEvent = (events & (CHECK_PHONE_STATE_PERMISSION_MASK
| ENFORCE_PHONE_STATE_PERMISSION_MASK)) != 0;
r.canReadPhoneState = isPhoneStateEvent && canReadPhoneState(callingPackage);
@@ -525,9 +534,7 @@
} else {//APP specify subID
r.subId = subId;
}
- r.phoneId = SubscriptionManager.getPhoneId(r.subId);
-
- int phoneId = r.phoneId;
+ r.phoneId = phoneId;
r.events = events;
if (DBG) {
log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
@@ -572,8 +579,10 @@
try {
if (DBG_LOC) log("listen: mCellLocation = "
+ mCellLocation[phoneId]);
- r.callback.onCellLocationChanged(
- new Bundle(mCellLocation[phoneId]));
+ if (checkLocationAccess(r)) {
+ r.callback.onCellLocationChanged(
+ new Bundle(mCellLocation[phoneId]));
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -619,7 +628,9 @@
try {
if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
+ mCellInfo.get(phoneId));
- r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
+ if (checkLocationAccess(r)) {
+ r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1013,14 +1024,14 @@
log("notifyCellInfoForSubscriber: subId=" + subId
+ " cellInfo=" + cellInfo);
}
-
+ int phoneId = SubscriptionManager.getPhoneId(subId);
synchronized (mRecords) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
if (validatePhoneId(phoneId)) {
mCellInfo.set(phoneId, cellInfo);
for (Record r : mRecords) {
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) &&
- idMatch(r.subId, subId, phoneId)) {
+ idMatch(r.subId, subId, phoneId) &&
+ checkLocationAccess(r)) {
try {
if (DBG_LOC) {
log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r);
@@ -1103,8 +1114,8 @@
log("notifyCallForwardingChangedForSubscriber: subId=" + subId
+ " cfi=" + cfi);
}
+ int phoneId = SubscriptionManager.getPhoneId(subId);
synchronized (mRecords) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
if (validatePhoneId(phoneId)) {
mCallForwarding[phoneId] = cfi;
for (Record r : mRecords) {
@@ -1131,8 +1142,8 @@
if (!checkNotifyPermission("notifyDataActivity()" )) {
return;
}
+ int phoneId = SubscriptionManager.getPhoneId(subId);
synchronized (mRecords) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
if (validatePhoneId(phoneId)) {
mDataActivity[phoneId] = state;
for (Record r : mRecords) {
@@ -1173,8 +1184,8 @@
+ "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
+ " mRecords.size()=" + mRecords.size());
}
+ int phoneId = SubscriptionManager.getPhoneId(subId);
synchronized (mRecords) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
if (validatePhoneId(phoneId)) {
boolean modified = false;
if (state == TelephonyManager.DATA_CONNECTED) {
@@ -1297,13 +1308,14 @@
log("notifyCellLocationForSubscriber: subId=" + subId
+ " cellLocation=" + cellLocation);
}
+ int phoneId = SubscriptionManager.getPhoneId(subId);
synchronized (mRecords) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
if (validatePhoneId(phoneId)) {
mCellLocation[phoneId] = cellLocation;
for (Record r : mRecords) {
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) &&
- idMatch(r.subId, subId, phoneId)) {
+ idMatch(r.subId, subId, phoneId) &&
+ checkLocationAccess(r)) {
try {
if (DBG_LOC) {
log("notifyCellLocation: cellLocation=" + cellLocation
@@ -1747,10 +1759,11 @@
boolean valid = false;
try {
foregroundUser = ActivityManager.getCurrentUser();
- valid = r.callerUserId == foregroundUser && r.matchPhoneStateListenerEvent(events);
+ valid = UserHandle.getUserId(r.callerUid) == foregroundUser
+ && r.matchPhoneStateListenerEvent(events);
if (DBG | DBG_LOC) {
log("validateEventsAndUserLocked: valid=" + valid
- + " r.callerUserId=" + r.callerUserId + " foregroundUser=" + foregroundUser
+ + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser
+ " r.events=" + r.events + " events=" + events);
}
} finally {
@@ -1782,6 +1795,16 @@
}
}
+ private boolean checkLocationAccess(Record r) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ return LocationAccessPolicy.canAccessCellLocation(mContext,
+ r.callingPackage, r.callerUid, r.callerPid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private void checkPossibleMissNotify(Record r, int phoneId) {
int events = r.events;
@@ -1829,7 +1852,9 @@
log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = "
+ mCellInfo.get(phoneId));
}
- r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
+ if (checkLocationAccess(r)) {
+ r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
+ }
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1877,7 +1902,9 @@
try {
if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = "
+ mCellLocation[phoneId]);
- r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId]));
+ if (checkLocationAccess(r)) {
+ r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId]));
+ }
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 8367916..4901192 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -65,7 +65,7 @@
static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
static final boolean DEBUG_OOM_ADJ = DEBUG_ALL || false;
static final boolean DEBUG_OOM_ADJ_REASON = DEBUG_ALL || false;
- static final boolean DEBUG_PAUSE = DEBUG_ALL || true;
+ static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
static final boolean DEBUG_POWER = DEBUG_ALL || false;
static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
static final boolean DEBUG_PROCESS_OBSERVERS = DEBUG_ALL || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index fa0df56..dadd869 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -442,12 +442,11 @@
options.setTaskOverlay(true, true /* canResume */);
}
}
- android.util.Log.d("bfranz", "I was here: " + mIsLockTask);
if (mIsLockTask) {
if (options == null) {
options = ActivityOptions.makeBasic();
}
- options.setLockTaskMode(true);
+ options.setLockTaskEnabled(true);
}
if (mWaitOption) {
result = mInterface.startActivityAndWait(null, null, intent, mimeType,
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index e2ceb31..db86f1a 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -2,7 +2,6 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -89,8 +88,10 @@
private int mCurrentTransitionDelayMs;
private boolean mLoggedTransitionStarting;
- private final SparseArray<StackTransitionInfo> mStackTransitionInfo = new SparseArray<>();
- private final SparseArray<StackTransitionInfo> mLastStackTransitionInfo = new SparseArray<>();
+ private final SparseArray<WindowingModeTransitionInfo> mWindowingModeTransitionInfo =
+ new SparseArray<>();
+ private final SparseArray<WindowingModeTransitionInfo> mLastWindowingModeTransitionInfo =
+ new SparseArray<>();
private final H mHandler;
private final class H extends Handler {
@@ -106,13 +107,13 @@
checkVisibility((TaskRecord) args.arg1, (ActivityRecord) args.arg2);
break;
case MSG_LOG_APP_START_MEMORY_STATE_CAPTURE:
- logAppStartMemoryStateCapture((StackTransitionInfo) msg.obj);
+ logAppStartMemoryStateCapture((WindowingModeTransitionInfo) msg.obj);
break;
}
}
};
- private final class StackTransitionInfo {
+ private final class WindowingModeTransitionInfo {
private ActivityRecord launchedActivity;
private int startResult;
private boolean currentTransitionProcessRunning;
@@ -242,56 +243,57 @@
// If we are already in an existing transition, only update the activity name, but not the
// other attributes.
- final int stackId = launchedActivity != null && launchedActivity.getStack() != null
- ? launchedActivity.getStack().mStackId
- : INVALID_STACK_ID;
+ final int windowingMode = launchedActivity != null
+ ? launchedActivity.getWindowingMode()
+ : WINDOWING_MODE_UNDEFINED;
if (mCurrentTransitionStartTime == INVALID_START_TIME) {
return;
}
- final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
+ final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
if (launchedActivity != null && info != null) {
info.launchedActivity = launchedActivity;
return;
}
- final boolean otherStacksLaunching = mStackTransitionInfo.size() > 0 && info == null;
+ final boolean otherWindowModesLaunching =
+ mWindowingModeTransitionInfo.size() > 0 && info == null;
if ((resultCode < 0 || launchedActivity == null || !processSwitch
- || stackId == INVALID_STACK_ID) && !otherStacksLaunching) {
+ || windowingMode == WINDOWING_MODE_UNDEFINED) && !otherWindowModesLaunching) {
// Failed to launch or it was not a process switch, so we don't care about the timing.
reset(true /* abort */);
return;
- } else if (otherStacksLaunching) {
- // Don't log this stack but continue with the other stacks.
+ } else if (otherWindowModesLaunching) {
+ // Don't log this windowing mode but continue with the other windowing modes.
return;
}
if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunched successful");
- final StackTransitionInfo newInfo = new StackTransitionInfo();
+ final WindowingModeTransitionInfo newInfo = new WindowingModeTransitionInfo();
newInfo.launchedActivity = launchedActivity;
newInfo.currentTransitionProcessRunning = processRunning;
newInfo.startResult = resultCode;
- mStackTransitionInfo.put(stackId, newInfo);
- mLastStackTransitionInfo.put(stackId, newInfo);
+ mWindowingModeTransitionInfo.put(windowingMode, newInfo);
+ mLastWindowingModeTransitionInfo.put(windowingMode, newInfo);
mCurrentTransitionDeviceUptime = (int) (SystemClock.uptimeMillis() / 1000);
}
/**
* Notifies the tracker that all windows of the app have been drawn.
*/
- void notifyWindowsDrawn(int stackId, long timestamp) {
- if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn stackId=" + stackId);
+ void notifyWindowsDrawn(int windowingMode, long timestamp) {
+ if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn windowingMode=" + windowingMode);
- final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
+ final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
if (info == null || info.loggedWindowsDrawn) {
return;
}
info.windowsDrawnDelayMs = calculateDelay(timestamp);
info.loggedWindowsDrawn = true;
- if (allStacksWindowsDrawn() && mLoggedTransitionStarting) {
+ if (allWindowsDrawn() && mLoggedTransitionStarting) {
reset(false /* abort */);
}
}
@@ -299,8 +301,8 @@
/**
* Notifies the tracker that the starting window was drawn.
*/
- void notifyStartingWindowDrawn(int stackId, long timestamp) {
- final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
+ void notifyStartingWindowDrawn(int windowingMode, long timestamp) {
+ final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
if (info == null || info.loggedStartingWindowDrawn) {
return;
}
@@ -311,25 +313,26 @@
/**
* Notifies the tracker that the app transition is starting.
*
- * @param stackIdReasons A map from stack id to a reason integer, which must be on of
- * ActivityManagerInternal.APP_TRANSITION_* reasons.
+ * @param windowingModeToReason A map from windowing mode to a reason integer, which must be on
+ * of ActivityManagerInternal.APP_TRANSITION_* reasons.
*/
- void notifyTransitionStarting(SparseIntArray stackIdReasons, long timestamp) {
+ void notifyTransitionStarting(SparseIntArray windowingModeToReason, long timestamp) {
if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
return;
}
if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting");
mCurrentTransitionDelayMs = calculateDelay(timestamp);
mLoggedTransitionStarting = true;
- for (int index = stackIdReasons.size() - 1; index >= 0; index--) {
- final int stackId = stackIdReasons.keyAt(index);
- final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
+ for (int index = windowingModeToReason.size() - 1; index >= 0; index--) {
+ final int windowingMode = windowingModeToReason.keyAt(index);
+ final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(
+ windowingMode);
if (info == null) {
continue;
}
- info.reason = stackIdReasons.valueAt(index);
+ info.reason = windowingModeToReason.valueAt(index);
}
- if (allStacksWindowsDrawn()) {
+ if (allWindowsDrawn()) {
reset(false /* abort */);
}
}
@@ -340,7 +343,8 @@
* @param activityRecord the app that is changing its visibility
*/
void notifyVisibilityChanged(ActivityRecord activityRecord) {
- final StackTransitionInfo info = mStackTransitionInfo.get(activityRecord.getStackId());
+ final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(
+ activityRecord.getWindowingMode());
if (info == null) {
return;
}
@@ -357,7 +361,8 @@
private void checkVisibility(TaskRecord t, ActivityRecord r) {
synchronized (mSupervisor.mService) {
- final StackTransitionInfo info = mStackTransitionInfo.get(r.getStackId());
+ final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(
+ r.getWindowingMode());
// If we have an active transition that's waiting on a certain activity that will be
// invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary.
@@ -365,8 +370,8 @@
if (DEBUG_METRICS) Slog.i(TAG, "notifyVisibilityChanged to invisible"
+ " activity=" + r);
logAppTransitionCancel(info);
- mStackTransitionInfo.remove(r.getStackId());
- if (mStackTransitionInfo.size() == 0) {
+ mWindowingModeTransitionInfo.remove(r.getWindowingMode());
+ if (mWindowingModeTransitionInfo.size() == 0) {
reset(true /* abort */);
}
}
@@ -379,8 +384,8 @@
* @param app The client into which we'll call bindApplication.
*/
void notifyBindApplication(ProcessRecord app) {
- for (int i = mStackTransitionInfo.size() - 1; i >= 0; i--) {
- final StackTransitionInfo info = mStackTransitionInfo.valueAt(i);
+ for (int i = mWindowingModeTransitionInfo.size() - 1; i >= 0; i--) {
+ final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.valueAt(i);
// App isn't attached to record yet, so match with info.
if (info.launchedActivity.appInfo == app.info) {
@@ -389,9 +394,9 @@
}
}
- private boolean allStacksWindowsDrawn() {
- for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
- if (!mStackTransitionInfo.valueAt(index).loggedWindowsDrawn) {
+ private boolean allWindowsDrawn() {
+ for (int index = mWindowingModeTransitionInfo.size() - 1; index >= 0; index--) {
+ if (!mWindowingModeTransitionInfo.valueAt(index).loggedWindowsDrawn) {
return false;
}
}
@@ -400,7 +405,7 @@
private boolean isAnyTransitionActive() {
return mCurrentTransitionStartTime != INVALID_START_TIME
- && mStackTransitionInfo.size() > 0;
+ && mWindowingModeTransitionInfo.size() > 0;
}
private void reset(boolean abort) {
@@ -411,7 +416,7 @@
mCurrentTransitionStartTime = INVALID_START_TIME;
mCurrentTransitionDelayMs = -1;
mLoggedTransitionStarting = false;
- mStackTransitionInfo.clear();
+ mWindowingModeTransitionInfo.clear();
}
private int calculateCurrentDelay() {
@@ -425,7 +430,7 @@
return (int) (timestamp - mCurrentTransitionStartTime);
}
- private void logAppTransitionCancel(StackTransitionInfo info) {
+ private void logAppTransitionCancel(WindowingModeTransitionInfo info) {
final int type = getTransitionType(info);
if (type == -1) {
return;
@@ -445,8 +450,8 @@
private void logAppTransitionMultiEvents() {
if (DEBUG_METRICS) Slog.i(TAG, "logging transition events");
- for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
- final StackTransitionInfo info = mStackTransitionInfo.valueAt(index);
+ for (int index = mWindowingModeTransitionInfo.size() - 1; index >= 0; index--) {
+ final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.valueAt(index);
final int type = getTransitionType(info);
if (type == -1) {
return;
@@ -513,7 +518,8 @@
}
void logAppTransitionReportedDrawn(ActivityRecord r, boolean restoredFromBundle) {
- final StackTransitionInfo info = mLastStackTransitionInfo.get(r.getStackId());
+ final WindowingModeTransitionInfo info = mLastWindowingModeTransitionInfo.get(
+ r.getWindowingMode());
if (info == null) {
return;
}
@@ -540,7 +546,7 @@
startupTimeMs);
}
- private int getTransitionType(StackTransitionInfo info) {
+ private int getTransitionType(WindowingModeTransitionInfo info) {
if (info.currentTransitionProcessRunning) {
if (info.startResult == START_SUCCESS) {
return TYPE_TRANSITION_WARM_LAUNCH;
@@ -553,7 +559,7 @@
return -1;
}
- private void logAppStartMemoryStateCapture(StackTransitionInfo info) {
+ private void logAppStartMemoryStateCapture(WindowingModeTransitionInfo info) {
final ProcessRecord processRecord = findProcessForActivity(info.launchedActivity);
if (processRecord == null) {
if (DEBUG_METRICS) Slog.i(TAG, "logAppStartMemoryStateCapture processRecord null");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index ddba349..4e60924 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1625,20 +1625,6 @@
// The activity may be waiting for stop, but that is no longer appropriate for it.
mStackSupervisor.mStoppingActivities.remove(this);
mStackSupervisor.mGoingToSleepActivities.remove(this);
-
- // If the activity is stopped or stopping, cycle to the paused state.
- if (state == STOPPED || state == STOPPING) {
- // Capture reason before state change
- final String reason = getLifecycleDescription("makeVisibleIfNeeded");
-
- // An activity must be in the {@link PAUSING} state for the system to validate
- // the move to {@link PAUSED}.
- state = PAUSING;
- service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
- PauseActivityItem.obtain(finishing, false /* userLeaving */,
- configChangeFlags, false /* dontReport */)
- .setDescription(reason));
- }
} catch (Exception e) {
// Just skip on any failure; we'll make it visible when it next restarts.
Slog.w(TAG, "Exception thrown making visibile: " + intent.getComponent(), e);
@@ -1901,14 +1887,15 @@
public void onStartingWindowDrawn(long timestamp) {
synchronized (service) {
mStackSupervisor.getActivityMetricsLogger().notifyStartingWindowDrawn(
- getStackId(), timestamp);
+ getWindowingMode(), timestamp);
}
}
@Override
public void onWindowsDrawn(long timestamp) {
synchronized (service) {
- mStackSupervisor.getActivityMetricsLogger().notifyWindowsDrawn(getStackId(), timestamp);
+ mStackSupervisor.getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(),
+ timestamp);
if (displayStartTime != 0) {
reportLaunchTimeLocked(timestamp);
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 812de88..fe10670 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1419,6 +1419,11 @@
return false;
}
+ if (prev == resuming) {
+ Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed");
+ return false;
+ }
+
if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSING: " + prev);
else if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Start pausing: " + prev);
mResumedActivity = null;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index a82facd..9a3b102 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3341,7 +3341,11 @@
stack.goToSleepIfPossible(false /* shuttingDown */);
} else {
stack.awakeFromSleepingLocked();
- if (isFocusedStack(stack)) {
+ if (isFocusedStack(stack)
+ && !mKeyguardController.isKeyguardActive(display.mDisplayId)) {
+ // If there is no keyguard on this display - resume immediately. Otherwise
+ // we'll wait for keyguard visibility callback and resume while ensuring
+ // activities visibility
resumeFocusedStackTopActivityLocked();
}
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 9776c4d..ed09879 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -793,10 +793,20 @@
AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
+
+ AppErrorDialog dialogToShow = null;
+ final String packageName;
+ final int userId;
synchronized (mService) {
- ProcessRecord proc = data.proc;
- AppErrorResult res = data.result;
- if (proc != null && proc.crashDialog != null) {
+ final ProcessRecord proc = data.proc;
+ final AppErrorResult res = data.result;
+ if (proc == null) {
+ Slog.e(TAG, "handleShowAppErrorUi: proc is null");
+ return;
+ }
+ packageName = proc.info.packageName;
+ userId = proc.userId;
+ if (proc.crashDialog != null) {
Slog.e(TAG, "App already has crash dialog: " + proc);
if (res != null) {
res.set(AppErrorDialog.ALREADY_SHOWING);
@@ -806,8 +816,8 @@
boolean isBackground = (UserHandle.getAppId(proc.uid)
>= Process.FIRST_APPLICATION_UID
&& proc.pid != MY_PID);
- for (int userId : mService.mUserController.getCurrentProfileIds()) {
- isBackground &= (proc.userId != userId);
+ for (int profileId : mService.mUserController.getCurrentProfileIds()) {
+ isBackground &= (userId != profileId);
}
if (isBackground && !showBackground) {
Slog.w(TAG, "Skipping crash dialog of " + proc + ": background");
@@ -828,7 +838,7 @@
mAppsNotReportingCrashes.contains(proc.info.packageName);
if ((mService.canShowErrorDialogs() || showBackground) && !crashSilenced
&& (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
- proc.crashDialog = new AppErrorDialog(mContext, mService, data);
+ proc.crashDialog = dialogToShow = new AppErrorDialog(mContext, mService, data);
} else {
// The device is asleep, so just pretend that the user
// saw a crash dialog and hit "force quit".
@@ -838,10 +848,9 @@
}
}
// If we've created a crash dialog, show it without the lock held
- if(data.proc.crashDialog != null) {
- Slog.i(TAG, "Showing crash dialog for package " + data.proc.info.packageName
- + " u" + data.proc.userId);
- data.proc.crashDialog.show();
+ if (dialogToShow != null) {
+ Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId);
+ dialogToShow.show();
}
}
@@ -1071,14 +1080,8 @@
// Bring up the infamous App Not Responding dialog
Message msg = Message.obtain();
- HashMap<String, Object> map = new HashMap<String, Object>();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
- msg.obj = map;
- msg.arg1 = aboveSystem ? 1 : 0;
- map.put("app", app);
- if (activity != null) {
- map.put("activity", activity);
- }
+ msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);
mService.mUiHandler.sendMessage(msg);
}
@@ -1095,11 +1098,15 @@
}
void handleShowAnrUi(Message msg) {
- Dialog d = null;
+ Dialog dialogToShow = null;
synchronized (mService) {
- HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
- ProcessRecord proc = (ProcessRecord)data.get("app");
- if (proc != null && proc.anrDialog != null) {
+ AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
+ final ProcessRecord proc = data.proc;
+ if (proc == null) {
+ Slog.e(TAG, "handleShowAnrUi: proc is null");
+ return;
+ }
+ if (proc.anrDialog != null) {
Slog.e(TAG, "App already has anr dialog: " + proc);
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
AppNotRespondingDialog.ALREADY_SHOWING);
@@ -1118,10 +1125,8 @@
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
if (mService.canShowErrorDialogs() || showBackground) {
- d = new AppNotRespondingDialog(mService,
- mContext, proc, (ActivityRecord)data.get("activity"),
- msg.arg1 != 0);
- proc.anrDialog = d;
+ dialogToShow = new AppNotRespondingDialog(mService, mContext, data);
+ proc.anrDialog = dialogToShow;
} else {
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
AppNotRespondingDialog.CANT_SHOW);
@@ -1130,8 +1135,8 @@
}
}
// If we've created a crash dialog, show it without the lock held
- if (d != null) {
- d.show();
+ if (dialogToShow != null) {
+ dialogToShow.show();
}
}
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index d9c6a30..8a88a69 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -21,7 +21,6 @@
import android.content.ActivityNotFoundException;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
@@ -49,36 +48,35 @@
private final ActivityManagerService mService;
private final ProcessRecord mProc;
- public AppNotRespondingDialog(ActivityManagerService service, Context context,
- ProcessRecord app, ActivityRecord activity, boolean aboveSystem) {
+ public AppNotRespondingDialog(ActivityManagerService service, Context context, Data data) {
super(context);
mService = service;
- mProc = app;
+ mProc = data.proc;
Resources res = context.getResources();
setCancelable(false);
int resid;
- CharSequence name1 = activity != null
- ? activity.info.loadLabel(context.getPackageManager())
+ CharSequence name1 = data.activity != null
+ ? data.activity.info.loadLabel(context.getPackageManager())
: null;
CharSequence name2 = null;
- if ((app.pkgList.size() == 1) &&
- (name2=context.getPackageManager().getApplicationLabel(app.info)) != null) {
+ if ((mProc.pkgList.size() == 1) &&
+ (name2=context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
if (name1 != null) {
resid = com.android.internal.R.string.anr_activity_application;
} else {
name1 = name2;
- name2 = app.processName;
+ name2 = mProc.processName;
resid = com.android.internal.R.string.anr_application_process;
}
} else {
if (name1 != null) {
- name2 = app.processName;
+ name2 = mProc.processName;
resid = com.android.internal.R.string.anr_activity_process;
} else {
- name1 = app.processName;
+ name1 = mProc.processName;
resid = com.android.internal.R.string.anr_process;
}
}
@@ -89,11 +87,11 @@
? res.getString(resid, bidi.unicodeWrap(name1.toString()), bidi.unicodeWrap(name2.toString()))
: res.getString(resid, bidi.unicodeWrap(name1.toString())));
- if (aboveSystem) {
+ if (data.aboveSystem) {
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
}
WindowManager.LayoutParams attrs = getWindow().getAttributes();
- attrs.setTitle("Application Not Responding: " + app.info.processName);
+ attrs.setTitle("Application Not Responding: " + mProc.info.processName);
attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
getWindow().setAttributes(attrs);
@@ -180,4 +178,16 @@
dismiss();
}
};
+
+ static class Data {
+ final ProcessRecord proc;
+ final ActivityRecord activity;
+ final boolean aboveSystem;
+
+ Data(ProcessRecord proc, ActivityRecord activity, boolean aboveSystem) {
+ this.proc = proc;
+ this.activity = activity;
+ this.aboveSystem = aboveSystem;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index 05305f3..e361a70 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -86,8 +86,16 @@
* display, false otherwise
*/
boolean isKeyguardShowing(int displayId) {
- return mKeyguardShowing && !mKeyguardGoingAway &&
- (displayId == DEFAULT_DISPLAY ? !mOccluded : displayId == mSecondaryDisplayShowing);
+ return isKeyguardActive(displayId) && !mKeyguardGoingAway;
+ }
+
+ /**
+ * @return true if Keyguard is showing and not occluded. We ignore whether it is going away or
+ * not here.
+ */
+ boolean isKeyguardActive(int displayId) {
+ return mKeyguardShowing && (displayId == DEFAULT_DISPLAY ? !mOccluded
+ : displayId == mSecondaryDisplayShowing);
}
/**
@@ -114,6 +122,9 @@
mDismissalRequested = false;
}
}
+ if (!showing) {
+ mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ }
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
updateKeyguardSleepToken();
}
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index e5762d2..af99111 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -38,7 +38,6 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
@@ -114,7 +113,7 @@
STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_HOME,
new Pair<>(StatusBarManager.DISABLE_HOME, StatusBarManager.DISABLE2_NONE));
- STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS,
+ STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW,
new Pair<>(StatusBarManager.DISABLE_RECENT, StatusBarManager.DISABLE2_NONE));
STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
@@ -308,7 +307,7 @@
private boolean isRecentsAllowed(int userId) {
return (getLockTaskFeaturesForUser(userId)
- & DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS) != 0;
+ & DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW) != 0;
}
private boolean isKeyguardAllowed(int userId) {
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 9d2a8e2..0dab528 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -215,6 +215,8 @@
private static final int REQUEST_SUPL_CONNECTION = 14;
private static final int RELEASE_SUPL_CONNECTION = 15;
private static final int REQUEST_LOCATION = 16;
+ private static final int REPORT_LOCATION = 17; // HAL reports location
+ private static final int REPORT_SV_STATUS = 18; // HAL reports SV status
// Request setid
private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1;
@@ -266,7 +268,7 @@
}
}
- // Simple class to hold stats reported in the Extras Bundle
+ // Threadsafe class to hold stats reported in the Extras Bundle
private static class LocationExtras {
private int mSvCount;
private int mMeanCn0;
@@ -278,9 +280,11 @@
}
public void set(int svCount, int meanCn0, int maxCn0) {
- mSvCount = svCount;
- mMeanCn0 = meanCn0;
- mMaxCn0 = maxCn0;
+ synchronized(this) {
+ mSvCount = svCount;
+ mMeanCn0 = meanCn0;
+ mMaxCn0 = maxCn0;
+ }
setBundle(mBundle);
}
@@ -291,14 +295,18 @@
// Also used by outside methods to add to other bundles
public void setBundle(Bundle extras) {
if (extras != null) {
- extras.putInt("satellites", mSvCount);
- extras.putInt("meanCn0", mMeanCn0);
- extras.putInt("maxCn0", mMaxCn0);
+ synchronized (this) {
+ extras.putInt("satellites", mSvCount);
+ extras.putInt("meanCn0", mMeanCn0);
+ extras.putInt("maxCn0", mMaxCn0);
+ }
}
}
public Bundle getBundle() {
- return mBundle;
+ synchronized (this) {
+ return new Bundle(mBundle);
+ }
}
}
@@ -411,7 +419,6 @@
private final Context mContext;
private final NtpTrustedTime mNtpTime;
private final ILocationManager mILocationManager;
- private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
private final LocationExtras mLocationExtras = new LocationExtras();
private final GnssStatusListenerHelper mListenerHelper;
private final GnssMeasurementsProvider mGnssMeasurementsProvider;
@@ -758,8 +765,6 @@
mNtpTime = NtpTrustedTime.getInstance(context);
mILocationManager = ilocationManager;
- mLocation.setExtras(mLocationExtras.getBundle());
-
// Create a wake lock
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
@@ -1726,6 +1731,10 @@
* called from native code to update our position.
*/
private void reportLocation(boolean hasLatLong, Location location) {
+ sendMessage(REPORT_LOCATION, hasLatLong ? 1 : 0, location);
+ }
+
+ private void handleReportLocation(boolean hasLatLong, Location location) {
if (location.hasSpeed()) {
mItarSpeedLimitExceeded = location.getSpeed() > ITAR_SPEED_LIMIT_METERS_PER_SECOND;
}
@@ -1739,18 +1748,15 @@
if (VERBOSE) Log.v(TAG, "reportLocation " + location.toString());
- synchronized (mLocation) {
- mLocation = location;
- // It would be nice to push the elapsed real-time timestamp
- // further down the stack, but this is still useful
- mLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
- mLocation.setExtras(mLocationExtras.getBundle());
+ // It would be nice to push the elapsed real-time timestamp
+ // further down the stack, but this is still useful
+ location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+ location.setExtras(mLocationExtras.getBundle());
- try {
- mILocationManager.reportLocation(mLocation, false);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException calling reportLocation");
- }
+ try {
+ mILocationManager.reportLocation(location, false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling reportLocation");
}
mGnssMetrics.logReceivedLocationStatus(hasLatLong);
@@ -1835,54 +1841,73 @@
}
}
+ // Helper class to carry data to handler for reportSvStatus
+ private static class SvStatusInfo {
+ public int mSvCount;
+ public int[] mSvidWithFlags;
+ public float[] mCn0s;
+ public float[] mSvElevations;
+ public float[] mSvAzimuths;
+ public float[] mSvCarrierFreqs;
+ }
+
/**
* called from native code to update SV info
*/
- private void reportSvStatus() {
- int svCount = native_read_sv_status(mSvidWithFlags,
- mCn0s,
- mSvElevations,
- mSvAzimuths,
- mSvCarrierFreqs);
+ private void reportSvStatus(int svCount, int[] svidWithFlags, float[] cn0s,
+ float[] svElevations, float[] svAzimuths, float[] svCarrierFreqs) {
+ SvStatusInfo svStatusInfo = new SvStatusInfo();
+ svStatusInfo.mSvCount = svCount;
+ svStatusInfo.mSvidWithFlags = svidWithFlags;
+ svStatusInfo.mCn0s = cn0s;
+ svStatusInfo.mSvElevations = svElevations;
+ svStatusInfo.mSvAzimuths = svAzimuths;
+ svStatusInfo.mSvCarrierFreqs = svCarrierFreqs;
+
+ sendMessage(REPORT_SV_STATUS, 0, svStatusInfo);
+ }
+
+ private void handleReportSvStatus(SvStatusInfo info) {
mListenerHelper.onSvStatusChanged(
- svCount,
- mSvidWithFlags,
- mCn0s,
- mSvElevations,
- mSvAzimuths,
- mSvCarrierFreqs);
+ info.mSvCount,
+ info.mSvidWithFlags,
+ info.mCn0s,
+ info.mSvElevations,
+ info.mSvAzimuths,
+ info.mSvCarrierFreqs);
// Log CN0 as part of GNSS metrics
- mGnssMetrics.logCn0(mCn0s, svCount);
+ mGnssMetrics.logCn0(info.mCn0s, info.mSvCount);
if (VERBOSE) {
- Log.v(TAG, "SV count: " + svCount);
+ Log.v(TAG, "SV count: " + info.mSvCount);
}
// Calculate number of satellites used in fix.
int usedInFixCount = 0;
int maxCn0 = 0;
int meanCn0 = 0;
- for (int i = 0; i < svCount; i++) {
- if ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0) {
+ for (int i = 0; i < info.mSvCount; i++) {
+ if ((info.mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0) {
++usedInFixCount;
- if (mCn0s[i] > maxCn0) {
- maxCn0 = (int) mCn0s[i];
+ if (info.mCn0s[i] > maxCn0) {
+ maxCn0 = (int) info.mCn0s[i];
}
- meanCn0 += mCn0s[i];
+ meanCn0 += info.mCn0s[i];
}
if (VERBOSE) {
- Log.v(TAG, "svid: " + (mSvidWithFlags[i] >> GnssStatus.SVID_SHIFT_WIDTH) +
- " cn0: " + mCn0s[i] +
- " elev: " + mSvElevations[i] +
- " azimuth: " + mSvAzimuths[i] +
- " carrier frequency: " + mSvCarrierFreqs[i] +
- ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) == 0
+ Log.v(TAG, "svid: " + (info.mSvidWithFlags[i] >> GnssStatus.SVID_SHIFT_WIDTH) +
+ " cn0: " + info.mCn0s[i] +
+ " elev: " + info.mSvElevations[i] +
+ " azimuth: " + info.mSvAzimuths[i] +
+ " carrier frequency: " + info.mSvCarrierFreqs[i] +
+ ((info.mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) == 0
? " " : " E") +
- ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA) == 0
+ ((info.mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA) == 0
? " " : " A") +
- ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) == 0
+ ((info.mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) == 0
? "" : "U") +
- ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY) == 0
+ ((info.mSvidWithFlags[i] &
+ GnssStatus.GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY) == 0
? "" : "F"));
}
}
@@ -2479,6 +2504,12 @@
case INITIALIZE_HANDLER:
handleInitialize();
break;
+ case REPORT_LOCATION:
+ handleReportLocation(msg.arg1 == 1, (Location) msg.obj);
+ break;
+ case REPORT_SV_STATUS:
+ handleReportSvStatus((SvStatusInfo) msg.obj);
+ break;
}
if (msg.arg2 == 1) {
// wakelock was taken for this message, release it
@@ -2798,6 +2829,10 @@
return "SUBSCRIPTION_OR_SIM_CHANGED";
case INITIALIZE_HANDLER:
return "INITIALIZE_HANDLER";
+ case REPORT_LOCATION:
+ return "REPORT_LOCATION";
+ case REPORT_SV_STATUS:
+ return "REPORT_SV_STATUS";
default:
return "<Unknown>";
}
@@ -2862,15 +2897,6 @@
}
}
- // for GPS SV statistics
- private static final int MAX_SVS = 64;
-
- // preallocated arrays, to avoid memory allocation in reportStatus()
- private int mSvidWithFlags[] = new int[MAX_SVS];
- private float mCn0s[] = new float[MAX_SVS];
- private float mSvElevations[] = new float[MAX_SVS];
- private float mSvAzimuths[] = new float[MAX_SVS];
- private float mSvCarrierFreqs[] = new float[MAX_SVS];
// preallocated to avoid memory allocation in reportNmea()
private byte[] mNmeaBuffer = new byte[120];
@@ -2899,11 +2925,6 @@
private native void native_delete_aiding_data(int flags);
- // returns number of SVs
- // mask[0] is ephemeris mask and mask[1] is almanac mask
- private native int native_read_sv_status(int[] prnWithFlags, float[] cn0s, float[] elevations,
- float[] azimuths, float[] carrierFrequencies);
-
private native int native_read_nmea(byte[] buffer, int bufferSize);
private native void native_inject_best_location(
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 21d86c4..ecff334 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1512,6 +1512,17 @@
}
}
+ /**
+ * Called when a {@link android.media.MediaSession2} instance is created.
+ * <p>
+ * This does two things.
+ * 1. Keep the newly created session in the service
+ * 2. Do sanity check to ensure unique id per package, and return result
+ *
+ * @param sessionToken SessionToken2 object in bundled form
+ * @return {@code true} if the session's id isn't used by the package now. {@code false}
+ * otherwise.
+ */
@Override
public boolean onSessionCreated(Bundle sessionToken) {
final int uid = Binder.getCallingUid();
@@ -1538,6 +1549,19 @@
}
}
+ /**
+ * Called when a {@link android.media.MediaSession2} instance is closed. (i.e. destroyed)
+ * <p>
+ * Ideally service should know that a session is destroyed through the
+ * {@link android.media.MediaController2.ControllerCallback#onDisconnected()}, which is
+ * asynchronous call. However, we also need synchronous way together to address timing
+ * issue. If the package recreates the session almost immediately, which happens commonly
+ * for tests, service will reject the creation through {@link #onSessionCreated(Bundle)}
+ * if the service hasn't notified previous destroy yet. This synchronous API will address
+ * the issue.
+ *
+ * @param sessionToken SessionToken2 object in bundled form
+ */
@Override
public void onSessionDestroyed(Bundle sessionToken) {
final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
index 184f8b2..d8a30ae 100644
--- a/services/core/java/com/android/server/notification/BadgeExtractor.java
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -61,4 +61,9 @@
public void setConfig(RankingConfig config) {
mConfig = config;
}
+
+ @Override
+ public void setZenHelper(ZenModeHelper helper) {
+
+ }
}
diff --git a/services/core/java/com/android/server/notification/ImportanceExtractor.java b/services/core/java/com/android/server/notification/ImportanceExtractor.java
index 452121c..dfdd55b 100644
--- a/services/core/java/com/android/server/notification/ImportanceExtractor.java
+++ b/services/core/java/com/android/server/notification/ImportanceExtractor.java
@@ -50,4 +50,9 @@
public void setConfig(RankingConfig config) {
mConfig = config;
}
+
+ @Override
+ public void setZenHelper(ZenModeHelper helper) {
+
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
index 3bfd93f..97bbc23 100644
--- a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
@@ -44,4 +44,9 @@
public void setConfig(RankingConfig config) {
// config is not used
}
+
+ @Override
+ public void setZenHelper(ZenModeHelper helper) {
+
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index 11c7ab7..d66fd57 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -52,4 +52,9 @@
public void setConfig(RankingConfig config) {
mConfig = config;
}
+
+ @Override
+ public void setZenHelper(ZenModeHelper helper) {
+
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
index 91fee46..bf777e0 100644
--- a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
@@ -85,4 +85,9 @@
public void setConfig(RankingConfig config) {
// ignore: config has no relevant information yet.
}
+
+ @Override
+ public void setZenHelper(ZenModeHelper helper) {
+
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c4953b5..ef354cd 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1312,11 +1312,6 @@
}
mUsageStats = usageStats;
mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
- mRankingHelper = new RankingHelper(getContext(),
- mPackageManagerClient,
- mRankingHandler,
- mUsageStats,
- extractorNames);
mConditionProviders = conditionProviders;
mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@@ -1335,6 +1330,7 @@
synchronized (mNotificationLock) {
updateInterruptionFilterLocked();
}
+ mRankingHandler.requestSort();
}
@Override
@@ -1342,6 +1338,12 @@
sendRegisteredOnlyBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED);
}
});
+ mRankingHelper = new RankingHelper(getContext(),
+ mPackageManagerClient,
+ mRankingHandler,
+ mZenModeHelper,
+ mUsageStats,
+ extractorNames);
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
@@ -4734,6 +4736,7 @@
ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
+ ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
orderBefore.add(r.getKey());
@@ -4744,6 +4747,7 @@
overridePeopleBefore.add(r.getPeopleOverride());
snoozeCriteriaBefore.add(r.getSnoozeCriteria());
userSentimentBefore.add(r.getUserSentiment());
+ suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
@@ -4756,7 +4760,9 @@
|| !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
|| !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
|| !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
- || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())) {
+ || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
+ || !Objects.equals(suppressVisuallyBefore.get(i),
+ r.getSuppressedVisualEffects())) {
mHandler.scheduleSendRankingUpdate();
return;
}
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index 31f8b27..24c1d59 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -22,6 +22,8 @@
* Extracts signals that will be useful to the {@link NotificationComparator} and caches them
* on the {@link NotificationRecord} object. These annotations will
* not be passed on to {@link android.service.notification.NotificationListenerService}s.
+ *
+ * If you add a new Extractor be sure to add it to R.array.config_notificationSignalExtractors.
*/
public interface NotificationSignalExtractor {
@@ -44,4 +46,10 @@
* @param config information about which signals are important.
*/
void setConfig(RankingConfig config);
+
+ /**
+ * @param helper Helper to determine what components of notifications should be blocked due to
+ * DND.
+ */
+ void setZenHelper(ZenModeHelper helper);
}
diff --git a/services/core/java/com/android/server/notification/PriorityExtractor.java b/services/core/java/com/android/server/notification/PriorityExtractor.java
index 7a287db..612c624 100644
--- a/services/core/java/com/android/server/notification/PriorityExtractor.java
+++ b/services/core/java/com/android/server/notification/PriorityExtractor.java
@@ -53,4 +53,9 @@
public void setConfig(RankingConfig config) {
mConfig = config;
}
+
+ @Override
+ public void setZenHelper(ZenModeHelper helper) {
+
+ }
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index dc936d2..b280bde 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -102,7 +102,7 @@
private SparseBooleanArray mBadgingEnabled;
public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
- NotificationUsageStats usageStats, String[] extractorNames) {
+ ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
mContext = context;
mRankingHandler = rankingHandler;
mPm = pm;
@@ -120,6 +120,7 @@
(NotificationSignalExtractor) extractorClass.newInstance();
extractor.initialize(mContext, usageStats);
extractor.setConfig(this);
+ extractor.setZenHelper(zenHelper);
mSignalExtractors[i] = extractor;
} catch (ClassNotFoundException e) {
Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index fd9ffb2..896480f 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -144,6 +144,11 @@
// ignore: config has no relevant information yet.
}
+ @Override
+ public void setZenHelper(ZenModeHelper helper) {
+
+ }
+
/**
* @param extras extras of the notification with EXTRA_PEOPLE populated
* @param timeoutMs timeout in milliseconds to wait for contacts response
@@ -444,7 +449,8 @@
private float mContactAffinity = NONE;
private NotificationRecord mRecord;
- private PeopleRankingReconsideration(Context context, String key, LinkedList<String> pendingLookups) {
+ private PeopleRankingReconsideration(Context context, String key,
+ LinkedList<String> pendingLookups) {
super(key);
mContext = context;
mPendingLookups = pendingLookups;
@@ -478,7 +484,9 @@
final String cacheKey = getCacheKey(mContext.getUserId(), handle);
mPeopleCache.put(cacheKey, lookupResult);
}
- if (DEBUG) Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
+ if (DEBUG) {
+ Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
+ }
mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
} else {
if (DEBUG) Slog.d(TAG, "lookupResult is null");
diff --git a/services/core/java/com/android/server/notification/VisibilityExtractor.java b/services/core/java/com/android/server/notification/VisibilityExtractor.java
index 37dbe3a..db54846 100644
--- a/services/core/java/com/android/server/notification/VisibilityExtractor.java
+++ b/services/core/java/com/android/server/notification/VisibilityExtractor.java
@@ -52,4 +52,9 @@
public void setConfig(RankingConfig config) {
mConfig = config;
}
+
+ @Override
+ public void setZenHelper(ZenModeHelper helper) {
+
+ }
}
diff --git a/services/core/java/com/android/server/notification/ZenModeExtractor.java b/services/core/java/com/android/server/notification/ZenModeExtractor.java
new file mode 100644
index 0000000..74f9806
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeExtractor.java
@@ -0,0 +1,73 @@
+/*
+* Copyright (C) 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.server.notification;
+
+import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * This {@link ZenModeExtractor} updates intercepted and visual interruption states.
+ */
+public class ZenModeExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "ZenModeExtractor";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private ZenModeHelper mZenModeHelper;
+
+ public void initialize(Context ctx, NotificationUsageStats usageStats) {
+ if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ }
+
+ public RankingReconsideration process(NotificationRecord record) {
+ if (record == null || record.getNotification() == null) {
+ if (DBG) Slog.d(TAG, "skipping empty notification");
+ return null;
+ }
+
+ if (mZenModeHelper == null) {
+ if (DBG) Slog.d(TAG, "skipping - no zen info available");
+ return null;
+ }
+
+ record.setIntercepted(mZenModeHelper.shouldIntercept(record));
+ if (record.isIntercepted()) {
+ int suppressed = (mZenModeHelper.shouldSuppressWhenScreenOff()
+ ? SUPPRESSED_EFFECT_SCREEN_OFF : 0)
+ | (mZenModeHelper.shouldSuppressWhenScreenOn()
+ ? SUPPRESSED_EFFECT_SCREEN_ON : 0);
+ record.setSuppressedVisualEffects(suppressed);
+ } else {
+ record.setSuppressedVisualEffects(0);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void setConfig(RankingConfig config) {
+ // ignore: config has no relevant information yet.
+ }
+
+ @Override
+ public void setZenHelper(ZenModeHelper helper) {
+ mZenModeHelper = helper;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4a2f577..256fb42 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -237,6 +237,7 @@
import android.security.KeyStore;
import android.security.SystemKeyStore;
import android.service.pm.PackageServiceDumpProto;
+import android.service.textclassifier.TextClassifierService;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
@@ -1392,6 +1393,7 @@
final @NonNull String mRequiredUninstallerPackage;
final @Nullable String mSetupWizardPackage;
final @Nullable String mStorageManagerPackage;
+ final @Nullable String mSystemTextClassifierPackage;
final @NonNull String mServicesSystemSharedLibraryPackageName;
final @NonNull String mSharedSystemSharedLibraryPackageName;
@@ -2952,6 +2954,9 @@
filter.setPriority(0);
}
}
+
+ mSystemTextClassifierPackage = getSystemTextClassifierPackageName();
+
mDeferProtectedFilters = false;
mProtectedFilters.clear();
@@ -20251,6 +20256,11 @@
}
@Override
+ public String getSystemTextClassifierPackageName() {
+ return mContext.getString(R.string.config_defaultTextClassifierPackage);
+ }
+
+ @Override
public void setApplicationEnabledSetting(String appPackageName,
int newState, int flags, int userId, String callingPackage) {
if (!sUserManager.exists(userId)) return;
@@ -23385,6 +23395,8 @@
return "android";
case PackageManagerInternal.PACKAGE_VERIFIER:
return mRequiredVerifierPackage;
+ case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER:
+ return mSystemTextClassifierPackage;
}
return null;
}
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 75a6106..bcf4b07 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -228,6 +228,10 @@
public boolean isVendorPrivileged() {
return (protectionLevel & PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED) != 0;
}
+ public boolean isSystemTextClassifier() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER)
+ != 0;
+ }
public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
if (!origPackageName.equals(sourcePackageName)) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index afaafbc..afa9dd0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1148,6 +1148,13 @@
// this app is a setup wizard, then it gets the permission.
allowed = true;
}
+ if (!allowed && bp.isSystemTextClassifier()
+ && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
+ UserHandle.USER_SYSTEM))) {
+ // Special permissions for the system default text classifier.
+ allowed = true;
+ }
}
return allowed;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 844aafb..397c50f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -859,6 +859,17 @@
}
}
+ public void scheduleTimeoutLocked() {
+ // If we didn't reset it right away, do so after we couldn't connect to
+ // it for an extended amount of time to avoid having a black wallpaper.
+ final Handler fgHandler = FgThread.getHandler();
+ fgHandler.removeCallbacks(mResetRunnable);
+ fgHandler.postDelayed(mResetRunnable, WALLPAPER_RECONNECT_TIMEOUT_MS);
+ if (DEBUG_LIVE) {
+ Slog.i(TAG, "Started wallpaper reconnect timeout for " + mWallpaper.wallpaperComponent);
+ }
+ }
+
private void processDisconnect(final ServiceConnection connection) {
synchronized (mLock) {
// The wallpaper disappeared. If this isn't a system-default one, track
@@ -883,13 +894,13 @@
} else {
mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
- // If we didn't reset it right away, do so after we couldn't connect to
- // it for an extended amount of time to avoid having a black wallpaper.
- final Handler fgHandler = FgThread.getHandler();
- fgHandler.removeCallbacks(mResetRunnable);
- fgHandler.postDelayed(mResetRunnable, WALLPAPER_RECONNECT_TIMEOUT_MS);
- if (DEBUG_LIVE) {
- Slog.i(TAG, "Started wallpaper reconnect timeout for " + wpService);
+ clearWallpaperComponentLocked(mWallpaper);
+ if (bindWallpaperComponentLocked(
+ wpService, false, false, mWallpaper, null)) {
+ mWallpaper.connection.scheduleTimeoutLocked();
+ } else {
+ Slog.w(TAG, "Reverting to built-in wallpaper!");
+ clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
}
}
final String flattened = wpService.flattenToString();
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index ed4543e..64f77a2 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -39,6 +39,12 @@
boolean getDetachWallpaper();
/**
+ * @return Whether we should show the wallpaper during the animation.
+ * @see Animation#getShowWallpaper()
+ */
+ boolean getShowWallpaper();
+
+ /**
* @return The background color behind the animation.
*/
@ColorInt int getBackgroundColor();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 8155656..42d6ec0 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -469,7 +469,7 @@
// never gets updated.
// If we're becoming invisible, update the client visibility if we are not running an
// animation. Otherwise, we'll update client visibility in onAnimationFinished.
- if (visible || !delayed) {
+ if (visible || !isReallyAnimating()) {
setClientHidden(!visible);
}
@@ -1668,6 +1668,9 @@
}
if (adapter != null) {
startAnimation(getPendingTransaction(), adapter, !isVisible());
+ if (adapter.getShowWallpaper()) {
+ mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ }
}
} else {
cancelAnimation();
@@ -1793,7 +1796,7 @@
"AppWindowToken");
clearThumbnail();
- setClientHidden(isHidden());
+ setClientHidden(hiddenRequested);
if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) {
getDisplayContent().computeImeTarget(true /* updateImeTarget */);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 473eeda..13357b8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -154,6 +154,7 @@
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.view.IInputMethodClient;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.utils.RotationCache;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -215,7 +216,8 @@
int mInitialDisplayDensity = 0;
DisplayCutout mInitialDisplayCutout;
- DisplayCutout mDisplayCutoutOverride;
+ private final RotationCache<DisplayCutout, DisplayCutout> mDisplayCutoutCache
+ = new RotationCache<>(this::calculateDisplayCutoutForRotationUncached);
/**
* Overridden display size. Initialized with {@link #mInitialDisplayWidth}
@@ -1198,7 +1200,11 @@
}
DisplayCutout calculateDisplayCutoutForRotation(int rotation) {
- final DisplayCutout cutout = mInitialDisplayCutout;
+ return mDisplayCutoutCache.getOrCompute(mInitialDisplayCutout, rotation);
+ }
+
+ private DisplayCutout calculateDisplayCutoutForRotationUncached(
+ DisplayCutout cutout, int rotation) {
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
return cutout;
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 423be63..5c8fadb 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -174,7 +174,7 @@
final int position = DockedDividerUtils.calculatePositionForBounds(mTmpRect, dockSide,
getContentWidth());
- DisplayCutout displayCutout = mDisplayContent.calculateDisplayCutoutForRotation(
+ final DisplayCutout displayCutout = mDisplayContent.calculateDisplayCutoutForRotation(
rotation);
// Since we only care about feasible states, snap to the closest snap target, like it
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 2173fa3..1b41cb8 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -43,6 +43,11 @@
}
@Override
+ public boolean getShowWallpaper() {
+ return mSpec.getShowWallpaper();
+ }
+
+ @Override
public int getBackgroundColor() {
return mSpec.getBackgroundColor();
}
@@ -82,6 +87,13 @@
}
/**
+ * @see AnimationAdapter#getShowWallpaper
+ */
+ default boolean getShowWallpaper() {
+ return false;
+ }
+
+ /**
* @see AnimationAdapter#getBackgroundColor
*/
default int getBackgroundColor() {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 78dd580..31b5c69 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -368,6 +368,11 @@
}
@Override
+ public boolean getShowWallpaper() {
+ return false;
+ }
+
+ @Override
public int getBackgroundColor() {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 35fc99f..ed6e606 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -221,6 +221,11 @@
}
@Override
+ public boolean getShowWallpaper() {
+ return false;
+ }
+
+ @Override
public int getBackgroundColor() {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a4f20b01..6356a35 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -606,8 +606,11 @@
// If we are ready to perform an app transition, check through all of the app tokens to be
// shown and see if they are ready to go.
if (mService.mAppTransition.isReady()) {
- defaultDisplay.pendingLayoutChanges |=
- surfacePlacer.handleAppTransitionReadyLocked();
+ // This needs to be split into two expressions, as handleAppTransitionReadyLocked may
+ // modify dc.pendingLayoutChanges, which would get lost when writing
+ // defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked()
+ final int layoutChanges = surfacePlacer.handleAppTransitionReadyLocked();
+ defaultDisplay.pendingLayoutChanges |= layoutChanges;
if (DEBUG_LAYOUT_REPEATS)
surfacePlacer.debugLayoutRepeats("after handleAppTransitionReadyLocked",
defaultDisplay.pendingLayoutChanges);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index f2ad6fb..a7d51f1 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -151,7 +151,10 @@
final RecentsAnimationController recentsAnimationController =
mService.getRecentsAnimationController();
- final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
+ final boolean animationWallpaper = w.mAppToken != null && w.mAppToken.getAnimation() != null
+ && w.mAppToken.getAnimation().getShowWallpaper();
+ final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
+ || animationWallpaper;
final boolean isRecentsTransitionTarget = (recentsAnimationController != null
&& recentsAnimationController.isWallpaperVisible(w));
if (isRecentsTransitionTarget) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 0863ee9..43fa3d5 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -69,6 +69,11 @@
}
@Override
+ public boolean getShowWallpaper() {
+ return mAnimation.getShowWallpaper();
+ }
+
+ @Override
public int getBackgroundColor() {
return mAnimation.getBackgroundColor();
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 7364e87..66c7293 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -566,14 +566,14 @@
if (!allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {
return false;
}
- final TaskStack stack = wtoken.getStack();
- final int stackId = stack != null ? stack.mStackId : INVALID_STACK_ID;
+ final int windowingMode = wtoken.getWindowingMode();
if (allDrawn) {
- outReasons.put(stackId, APP_TRANSITION_WINDOWS_DRAWN);
+ outReasons.put(windowingMode, APP_TRANSITION_WINDOWS_DRAWN);
} else {
- outReasons.put(stackId, wtoken.startingData instanceof SplashScreenStartingData
- ? APP_TRANSITION_SPLASH_SCREEN
- : APP_TRANSITION_SNAPSHOT);
+ outReasons.put(windowingMode,
+ wtoken.startingData instanceof SplashScreenStartingData
+ ? APP_TRANSITION_SPLASH_SCREEN
+ : APP_TRANSITION_SNAPSHOT);
}
}
diff --git a/services/core/java/com/android/server/wm/utils/RotationCache.java b/services/core/java/com/android/server/wm/utils/RotationCache.java
new file mode 100644
index 0000000..7a06cbc
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/RotationCache.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import android.util.SparseArray;
+
+import java.util.Arrays;
+
+/**
+ * Caches the result of a rotation-dependent computation.
+ *
+ * The cache is discarded once the identity of the other parameter changes.
+ *
+ * @param <T> type of the parameter to the computation
+ * @param <R> type of the result of the computation
+ */
+public class RotationCache<T,R> {
+
+ private final RotationDependentComputation<T,R> mComputation;
+ private final SparseArray<R> mCache = new SparseArray<>(4);
+ private T mCachedFor;
+
+ public RotationCache(RotationDependentComputation<T, R> computation) {
+ mComputation = computation;
+ }
+
+ /**
+ * Looks up the result of the computation, or calculates it if needed.
+ *
+ * @param t a parameter to the rotation-dependent computation.
+ * @param rotation the rotation for which to perform the rotation-dependent computation.
+ * @return the result of the rotation-dependent computation.
+ */
+ public R getOrCompute(T t, int rotation) {
+ if (t != mCachedFor) {
+ mCache.clear();
+ mCachedFor = t;
+ }
+ final int idx = mCache.indexOfKey(rotation);
+ if (idx >= 0) {
+ return mCache.valueAt(idx);
+ }
+ final R result = mComputation.compute(t, rotation);
+ mCache.put(rotation, result);
+ return result;
+ }
+
+ /**
+ * A computation that takes a generic input and is dependent on the rotation. The result can
+ * be cached by {@link RotationCache}.
+ */
+ @FunctionalInterface
+ public interface RotationDependentComputation<T, R> {
+ R compute(T t, int rotation);
+ }
+}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index c2771e4..0a1da57 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -99,14 +99,16 @@
using android::hardware::gnss::V1_0::IGnssDebug;
using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
using android::hardware::gnss::V1_0::IGnssGeofencing;
-using android::hardware::gnss::V1_0::IGnssMeasurementCallback;
+using IGnssMeasurementCallback_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurementCallback;
using android::hardware::gnss::V1_0::IGnssNavigationMessage;
using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback;
using android::hardware::gnss::V1_0::IGnssNi;
using android::hardware::gnss::V1_0::IGnssNiCallback;
using android::hardware::gnss::V1_0::IGnssXtra;
using android::hardware::gnss::V1_0::IGnssXtraCallback;
+
using android::hardware::gnss::V1_1::IGnssCallback;
+using android::hardware::gnss::V1_1::IGnssMeasurementCallback;
struct GnssDeathRecipient : virtual public hidl_death_recipient
{
@@ -393,10 +395,7 @@
// New in 1.1
Return<void> gnssNameCb(const android::hardware::hidl_string& name) override;
- static GnssSvInfo sGnssSvList[static_cast<uint32_t>(
- android::hardware::gnss::V1_0::GnssMax::SVS_COUNT)];
- static size_t sGnssSvListSize;
-
+ // TODO(b/73306084): Reconsider allocation cost vs threadsafety on these statics
static const char* sNmeaString;
static size_t sNmeaStringLength;
};
@@ -412,11 +411,8 @@
return Void();
}
-IGnssCallback::GnssSvInfo GnssCallback::sGnssSvList[static_cast<uint32_t>(
- android::hardware::gnss::V1_0::GnssMax::SVS_COUNT)];
const char* GnssCallback::sNmeaString = nullptr;
size_t GnssCallback::sNmeaStringLength = 0;
-size_t GnssCallback::sGnssSvListSize = 0;
Return<void> GnssCallback::gnssLocationCb(const GnssLocation& location) {
JNIEnv* env = getJniEnv();
@@ -430,6 +426,7 @@
boolToJbool(hasLatLong),
jLocation);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ env->DeleteLocalRef(jLocation);
return Void();
}
@@ -443,20 +440,55 @@
Return<void> GnssCallback::gnssSvStatusCb(const IGnssCallback::GnssSvStatus& svStatus) {
JNIEnv* env = getJniEnv();
- sGnssSvListSize = svStatus.numSvs;
- if (sGnssSvListSize > static_cast<uint32_t>(
+ uint32_t listSize = svStatus.numSvs;
+ if (listSize > static_cast<uint32_t>(
android::hardware::gnss::V1_0::GnssMax::SVS_COUNT)) {
- ALOGD("Too many satellites %zd. Clamps to %u.", sGnssSvListSize,
+ ALOGD("Too many satellites %u. Clamps to %u.", listSize,
static_cast<uint32_t>(android::hardware::gnss::V1_0::GnssMax::SVS_COUNT));
- sGnssSvListSize = static_cast<uint32_t>(android::hardware::gnss::V1_0::GnssMax::SVS_COUNT);
+ listSize = static_cast<uint32_t>(android::hardware::gnss::V1_0::GnssMax::SVS_COUNT);
}
- // Copy GNSS SV info into sGnssSvList, if any.
- if (svStatus.numSvs > 0) {
- memcpy(sGnssSvList, svStatus.gnssSvList.data(), sizeof(GnssSvInfo) * sGnssSvListSize);
+ jintArray svidWithFlagArray = env->NewIntArray(listSize);
+ jfloatArray cn0Array = env->NewFloatArray(listSize);
+ jfloatArray elevArray = env->NewFloatArray(listSize);
+ jfloatArray azimArray = env->NewFloatArray(listSize);
+ jfloatArray carrierFreqArray = env->NewFloatArray(listSize);
+
+ jint* svidWithFlags = env->GetIntArrayElements(svidWithFlagArray, 0);
+ jfloat* cn0s = env->GetFloatArrayElements(cn0Array, 0);
+ jfloat* elev = env->GetFloatArrayElements(elevArray, 0);
+ jfloat* azim = env->GetFloatArrayElements(azimArray, 0);
+ jfloat* carrierFreq = env->GetFloatArrayElements(carrierFreqArray, 0);
+
+ /*
+ * Read GNSS SV info.
+ */
+ for (size_t i = 0; i < listSize; ++i) {
+ enum ShiftWidth: uint8_t {
+ SVID_SHIFT_WIDTH = 8,
+ CONSTELLATION_TYPE_SHIFT_WIDTH = 4
+ };
+
+ const IGnssCallback::GnssSvInfo& info = svStatus.gnssSvList.data()[i];
+ svidWithFlags[i] = (info.svid << SVID_SHIFT_WIDTH) |
+ (static_cast<uint32_t>(info.constellation) << CONSTELLATION_TYPE_SHIFT_WIDTH) |
+ static_cast<uint32_t>(info.svFlag);
+ cn0s[i] = info.cN0Dbhz;
+ elev[i] = info.elevationDegrees;
+ azim[i] = info.azimuthDegrees;
+ carrierFreq[i] = info.carrierFrequencyHz;
}
- env->CallVoidMethod(mCallbacksObj, method_reportSvStatus);
+ env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0);
+ env->ReleaseFloatArrayElements(cn0Array, cn0s, 0);
+ env->ReleaseFloatArrayElements(elevArray, elev, 0);
+ env->ReleaseFloatArrayElements(azimArray, azim, 0);
+ env->ReleaseFloatArrayElements(carrierFreqArray, carrierFreq, 0);
+
+ env->CallVoidMethod(mCallbacksObj, method_reportSvStatus,
+ static_cast<jint>(listSize), svidWithFlagArray, cn0Array, elevArray, azimArray,
+ carrierFreqArray);
+
checkAndClearExceptionFromCallback(env, __FUNCTION__);
return Void();
}
@@ -575,6 +607,7 @@
timestamp);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ env->DeleteLocalRef(jLocation);
return Void();
}
@@ -590,6 +623,7 @@
status,
jLocation);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ env->DeleteLocalRef(jLocation);
return Void();
}
@@ -699,21 +733,24 @@
* GnssMeasurement interface.
*/
struct GnssMeasurementCallback : public IGnssMeasurementCallback {
- Return<void> GnssMeasurementCb(const IGnssMeasurementCallback::GnssData& data);
+ Return<void> gnssMeasurementCb(const IGnssMeasurementCallback::GnssData& data) override;
+ Return<void> GnssMeasurementCb(const IGnssMeasurementCallback_V1_0::GnssData& data) override;
private:
- jobject translateGnssMeasurement(
- JNIEnv* env, const IGnssMeasurementCallback::GnssMeasurement* measurement);
- jobject translateGnssClock(
- JNIEnv* env, const IGnssMeasurementCallback::GnssClock* clock);
+ void translateGnssMeasurement_V1_0(
+ JNIEnv* env, const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurement,
+ JavaObject object);
jobjectArray translateGnssMeasurements(
JNIEnv* env,
const IGnssMeasurementCallback::GnssMeasurement* measurements,
+ const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurements_v1_0,
size_t count);
+ jobject translateGnssClock(
+ JNIEnv* env, const IGnssMeasurementCallback::GnssClock* clock);
void setMeasurementData(JNIEnv* env, jobject clock, jobjectArray measurementArray);
};
-Return<void> GnssMeasurementCallback::GnssMeasurementCb(
+Return<void> GnssMeasurementCallback::gnssMeasurementCb(
const IGnssMeasurementCallback::GnssData& data) {
JNIEnv* env = getJniEnv();
@@ -722,7 +759,7 @@
clock = translateGnssClock(env, &data.clock);
measurementArray = translateGnssMeasurements(
- env, data.measurements.data(), data.measurementCount);
+ env, data.measurements.data(), NULL, data.measurements.size());
setMeasurementData(env, clock, measurementArray);
env->DeleteLocalRef(clock);
@@ -730,10 +767,27 @@
return Void();
}
-jobject GnssMeasurementCallback::translateGnssMeasurement(
- JNIEnv* env, const IGnssMeasurementCallback::GnssMeasurement* measurement) {
- JavaObject object(env, "android/location/GnssMeasurement");
+Return<void> GnssMeasurementCallback::GnssMeasurementCb(
+ const IGnssMeasurementCallback_V1_0::GnssData& data) {
+ JNIEnv* env = getJniEnv();
+ jobject clock;
+ jobjectArray measurementArray;
+
+ clock = translateGnssClock(env, &data.clock);
+ measurementArray = translateGnssMeasurements(
+ env, NULL, data.measurements.data(), data.measurementCount);
+ setMeasurementData(env, clock, measurementArray);
+
+ env->DeleteLocalRef(clock);
+ env->DeleteLocalRef(measurementArray);
+ return Void();
+}
+
+// preallocate object as: JavaObject object(env, "android/location/GnssMeasurement");
+void GnssMeasurementCallback::translateGnssMeasurement_V1_0(
+ JNIEnv* env, const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurement,
+ JavaObject object) {
uint32_t flags = static_cast<uint32_t>(measurement->flags);
SET(Svid, static_cast<int32_t>(measurement->svid));
@@ -757,13 +811,8 @@
SET(CarrierFrequencyHz, measurement->carrierFrequencyHz);
}
- if (flags & static_cast<uint32_t>(GnssMeasurementFlags::HAS_CARRIER_PHASE)) {
- SET(CarrierPhase, measurement->carrierPhase);
- }
-
- if (flags & static_cast<uint32_t>(GnssMeasurementFlags::HAS_CARRIER_PHASE_UNCERTAINTY)) {
- SET(CarrierPhaseUncertainty, measurement->carrierPhaseUncertainty);
- }
+ // Intentionally not copying deprecated fields of carrierCycles,
+ // carrierPhase, carrierPhaseUncertainty
SET(MultipathIndicator, static_cast<int32_t>(measurement->multipathIndicator));
@@ -774,8 +823,6 @@
if (flags & static_cast<uint32_t>(GnssMeasurementFlags::HAS_AUTOMATIC_GAIN_CONTROL)) {
SET(AutomaticGainControlLevelInDb, measurement->agcLevelDb);
}
-
- return object.get();
}
jobject GnssMeasurementCallback::translateGnssClock(
@@ -818,8 +865,9 @@
}
jobjectArray GnssMeasurementCallback::translateGnssMeasurements(JNIEnv* env,
- const IGnssMeasurementCallback::GnssMeasurement*
- measurements, size_t count) {
+ const IGnssMeasurementCallback::GnssMeasurement* measurements,
+ const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurements_v1_0,
+ size_t count) {
if (count == 0) {
return NULL;
}
@@ -831,11 +879,18 @@
NULL /* initialElement */);
for (uint16_t i = 0; i < count; ++i) {
- jobject gnssMeasurement = translateGnssMeasurement(
- env,
- &measurements[i]);
- env->SetObjectArrayElement(gnssMeasurementArray, i, gnssMeasurement);
- env->DeleteLocalRef(gnssMeasurement);
+ JavaObject object(env, "android/location/GnssMeasurement");
+ if (measurements != NULL) {
+ translateGnssMeasurement_V1_0(env, &(measurements[i].v1_0), object);
+
+ // Set the V1_1 flag
+ SET(AccumulatedDeltaRangeState,
+ static_cast<int32_t>(measurements[i].accumulatedDeltaRangeState));
+ } else {
+ translateGnssMeasurement_V1_0(env, &(measurements_v1_0[i]), object);
+ }
+
+ env->SetObjectArrayElement(gnssMeasurementArray, i, object.get());
}
env->DeleteLocalRef(gnssMeasurementClass);
@@ -1063,7 +1118,7 @@
method_reportLocation = env->GetMethodID(clazz, "reportLocation",
"(ZLandroid/location/Location;)V");
method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
- method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V");
+ method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "(I[I[F[F[F[F)V");
method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II[B)V");
method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V");
@@ -1369,49 +1424,6 @@
}
}
-/*
- * This enum is used by the read_sv_status method to combine the svid,
- * constellation and svFlag fields.
- */
-enum ShiftWidth: uint8_t {
- SVID_SHIFT_WIDTH = 8,
- CONSTELLATION_TYPE_SHIFT_WIDTH = 4
-};
-
-static jint android_location_GnssLocationProvider_read_sv_status(JNIEnv* env, jobject /* obj */,
- jintArray svidWithFlagArray, jfloatArray cn0Array, jfloatArray elevArray,
- jfloatArray azumArray, jfloatArray carrierFreqArray) {
- /*
- * This method should only be called from within a call to reportSvStatus.
- */
- jint* svidWithFlags = env->GetIntArrayElements(svidWithFlagArray, 0);
- jfloat* cn0s = env->GetFloatArrayElements(cn0Array, 0);
- jfloat* elev = env->GetFloatArrayElements(elevArray, 0);
- jfloat* azim = env->GetFloatArrayElements(azumArray, 0);
- jfloat* carrierFreq = env->GetFloatArrayElements(carrierFreqArray, 0);
-
- /*
- * Read GNSS SV info.
- */
- for (size_t i = 0; i < GnssCallback::sGnssSvListSize; ++i) {
- const IGnssCallback::GnssSvInfo& info = GnssCallback::sGnssSvList[i];
- svidWithFlags[i] = (info.svid << SVID_SHIFT_WIDTH) |
- (static_cast<uint32_t>(info.constellation) << CONSTELLATION_TYPE_SHIFT_WIDTH) |
- static_cast<uint32_t>(info.svFlag);
- cn0s[i] = info.cN0Dbhz;
- elev[i] = info.elevationDegrees;
- azim[i] = info.azimuthDegrees;
- carrierFreq[i] = info.carrierFrequencyHz;
- }
-
- env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0);
- env->ReleaseFloatArrayElements(cn0Array, cn0s, 0);
- env->ReleaseFloatArrayElements(elevArray, elev, 0);
- env->ReleaseFloatArrayElements(azumArray, azim, 0);
- env->ReleaseFloatArrayElements(carrierFreqArray, carrierFreq, 0);
- return static_cast<jint>(GnssCallback::sGnssSvListSize);
-}
-
static void android_location_GnssLocationProvider_agps_set_reference_location_cellid(
JNIEnv* /* env */, jobject /* obj */, jint type, jint mcc, jint mnc, jint lac, jint cid) {
IAGnssRil::AGnssRefLocation location;
@@ -2057,9 +2069,6 @@
{"native_delete_aiding_data",
"(I)V",
reinterpret_cast<void*>(android_location_GnssLocationProvider_delete_aiding_data)},
- {"native_read_sv_status",
- "([I[F[F[F[F)I",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_read_sv_status)},
{"native_read_nmea", "([BI)I", reinterpret_cast<void *>(
android_location_GnssLocationProvider_read_nmea)},
{"native_inject_time", "(JJI)V", reinterpret_cast<void *>(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8753344..29d5d54 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -50,6 +50,8 @@
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
@@ -9817,6 +9819,13 @@
@Override
public void setLockTaskFeatures(ComponentName who, int flags) {
Preconditions.checkNotNull(who, "ComponentName is null");
+
+ // Throw if Overview is used without Home.
+ boolean hasHome = (flags & LOCK_TASK_FEATURE_HOME) != 0;
+ boolean hasOverview = (flags & LOCK_TASK_FEATURE_OVERVIEW) != 0;
+ Preconditions.checkArgument(hasHome || !hasOverview,
+ "Cannot use LOCK_TASK_FEATURE_OVERVIEW without LOCK_TASK_FEATURE_HOME");
+
final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
enforceCanCallLockTaskLocked(who);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index 9923fa8..d3df924 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -108,7 +108,8 @@
assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0);
}
- @Test
+ // TODO: b/71582913
+ //@Test
public void testPausingWhenVisibleFromStopped() throws Exception {
final MutableBoolean pauseFound = new MutableBoolean(false);
doAnswer((InvocationOnMock invocationOnMock) -> {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 00a85a5..f58766f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -94,7 +94,6 @@
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -3765,19 +3764,22 @@
// The DO can still set lock task packages
final String[] doPackages = {"doPackage1", "doPackage2"};
final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
+ | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
verifyCanSetLockTask(DpmMockContext.CALLER_SYSTEM_USER_UID, UserHandle.USER_SYSTEM, admin1, doPackages, flags);
final String[] secondaryPoPackages = {"secondaryPoPackage1", "secondaryPoPackage2"};
final int secondaryPoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
+ | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
verifyCanNotSetLockTask(DpmMockContext.CALLER_UID, admin3, secondaryPoPackages, secondaryPoFlags);
// Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
final String[] poPackages = {"poPackage1", "poPackage2"};
final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
+ | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, poPackages, poFlags);
// Setting same affiliation ids
@@ -3820,7 +3822,8 @@
final String[] poPackages = {"poPackage1", "poPackage2"};
final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
+ | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
verifyCanSetLockTask(DpmMockContext.CALLER_UID, DpmMockContext.CALLER_USER_HANDLE, admin1,
poPackages, poFlags);
@@ -3836,10 +3839,25 @@
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
final String[] mpoPackages = {"poPackage1", "poPackage2"};
final int mpoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
+ | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, mpoPackages, mpoFlags);
}
+ public void testLockTaskFeatures_IllegalArgumentException() throws Exception {
+ // Setup a device owner.
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ // Lock task policy is updated when loading user data.
+ verifyLockTaskState(UserHandle.USER_SYSTEM);
+
+ final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+ assertExpectException(IllegalArgumentException.class,
+ "Cannot use LOCK_TASK_FEATURE_OVERVIEW without LOCK_TASK_FEATURE_HOME",
+ () -> dpm.setLockTaskFeatures(admin1, flags));
+ }
+
public void testIsDeviceManaged() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java
new file mode 100644
index 0000000..d3cf978
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import static android.util.Pair.create;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+
+import com.android.server.wm.utils.RotationCache.RotationDependentComputation;
+
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class RotationCacheTest {
+
+ private RotationCache<Object, Pair<Object, Integer>> mCache;
+ private boolean mComputationCalled;
+
+ @Before
+ public void setUp() throws Exception {
+ mComputationCalled = false;
+ mCache = new RotationCache<>((o, rot) -> {
+ mComputationCalled = true;
+ return create(o, rot);
+ });
+ }
+
+ @Test
+ public void getOrCompute_computes() throws Exception {
+ assertThat(mCache.getOrCompute("hello", 0), equalTo(create("hello", 0)));
+ assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1)));
+ assertThat(mCache.getOrCompute("hello", 2), equalTo(create("hello", 2)));
+ assertThat(mCache.getOrCompute("hello", 3), equalTo(create("hello", 3)));
+ }
+
+ @Test
+ public void getOrCompute_sameParam_sameRot_hitsCache() throws Exception {
+ assertNotNull(mCache.getOrCompute("hello", 1));
+
+ mComputationCalled = false;
+ assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1)));
+ assertThat(mComputationCalled, is(false));
+ }
+
+ @Test
+ public void getOrCompute_sameParam_hitsCache_forAllRots() throws Exception {
+ assertNotNull(mCache.getOrCompute("hello", 3));
+ assertNotNull(mCache.getOrCompute("hello", 2));
+ assertNotNull(mCache.getOrCompute("hello", 1));
+ assertNotNull(mCache.getOrCompute("hello", 0));
+
+ mComputationCalled = false;
+ assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1)));
+ assertThat(mCache.getOrCompute("hello", 0), equalTo(create("hello", 0)));
+ assertThat(mCache.getOrCompute("hello", 2), equalTo(create("hello", 2)));
+ assertThat(mCache.getOrCompute("hello", 3), equalTo(create("hello", 3)));
+ assertThat(mComputationCalled, is(false));
+ }
+
+ @Test
+ public void getOrCompute_changingParam_recomputes() throws Exception {
+ assertNotNull(mCache.getOrCompute("hello", 1));
+
+ assertThat(mCache.getOrCompute("world", 1), equalTo(create("world", 1)));
+ }
+
+ @Test
+ public void getOrCompute_changingParam_clearsCacheForDifferentRots() throws Exception {
+ assertNotNull(mCache.getOrCompute("hello", 1));
+ assertNotNull(mCache.getOrCompute("world", 2));
+
+ mComputationCalled = false;
+ assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1)));
+ assertThat(mComputationCalled, is(true));
+ }
+}
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 9ebce71..354d2d5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -161,8 +161,8 @@
when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
.thenReturn(SOUND_URI);
- mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
- new String[] {ImportanceExtractor.class.getName()});
+ mHelper = new RankingHelper(getContext(), mPm, mHandler, mock(ZenModeHelper.class),
+ mUsageStats, new String[] {ImportanceExtractor.class.getName()});
mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("A")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeExtractorTest.java
new file mode 100644
index 0000000..faba6b6
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeExtractorTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ZenModeExtractorTest extends UiServiceTestCase {
+
+ @Mock
+ ZenModeHelper mZenModeHelper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testExtractIntercepted() {
+ ZenModeExtractor extractor = new ZenModeExtractor();
+ extractor.setZenHelper(mZenModeHelper);
+ NotificationRecord r = generateRecord();
+
+ assertFalse(r.isIntercepted());
+
+ when(mZenModeHelper.shouldIntercept(any())).thenReturn(true);
+
+ extractor.process(r);
+
+ assertTrue(r.isIntercepted());
+ }
+
+ @Test
+ public void testExtractVisualDisturbancesNotIntercepted() {
+ ZenModeExtractor extractor = new ZenModeExtractor();
+ extractor.setZenHelper(mZenModeHelper);
+ NotificationRecord r = generateRecord();
+
+ when(mZenModeHelper.shouldIntercept(any())).thenReturn(false);
+ when(mZenModeHelper.shouldSuppressWhenScreenOff()).thenReturn(false);
+
+ extractor.process(r);
+
+ assertEquals(0, r.getSuppressedVisualEffects());
+ }
+
+ @Test
+ public void testExtractVisualDisturbancesIntercepted() {
+ ZenModeExtractor extractor = new ZenModeExtractor();
+ extractor.setZenHelper(mZenModeHelper);
+ NotificationRecord r = generateRecord();
+
+ when(mZenModeHelper.shouldIntercept(any())).thenReturn(true);
+ when(mZenModeHelper.shouldSuppressWhenScreenOff()).thenReturn(true);
+ when(mZenModeHelper.shouldSuppressWhenScreenOn()).thenReturn(true);
+
+ extractor.process(r);
+
+ assertEquals(NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF
+ | NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON,
+ r.getSuppressedVisualEffects());
+ }
+
+ private NotificationRecord generateRecord() {
+ NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
+ final Notification.Builder builder = new Notification.Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ Notification n = builder.build();
+ StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0,
+ 0, n, UserHandle.ALL, null, System.currentTimeMillis());
+ return new NotificationRecord(getContext(), sbn, channel);
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 25c54b3..f4bb32d 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -34,6 +34,7 @@
import android.hardware.soundtrigger.SoundTrigger.SoundModel;
import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
import android.hardware.soundtrigger.SoundTriggerModule;
+import android.os.Binder;
import android.os.DeadObjectException;
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
@@ -880,21 +881,26 @@
}
private void initializeTelephonyAndPowerStateListeners() {
- // Get the current call state synchronously for the first recognition.
- mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
+ long token = Binder.clearCallingIdentity();
+ try {
+ // Get the current call state synchronously for the first recognition.
+ mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
- // Register for call state changes when the first call to start recognition occurs.
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+ // Register for call state changes when the first call to start recognition occurs.
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- // Register for power saver mode changes when the first call to start recognition
- // occurs.
- if (mPowerSaveModeListener == null) {
- mPowerSaveModeListener = new PowerSaveModeListener();
- mContext.registerReceiver(mPowerSaveModeListener,
- new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+ // Register for power saver mode changes when the first call to start recognition
+ // occurs.
+ if (mPowerSaveModeListener == null) {
+ mPowerSaveModeListener = new PowerSaveModeListener();
+ mContext.registerReceiver(mPowerSaveModeListener,
+ new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+ }
+ mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND)
+ .batterySaverEnabled;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND)
- .batterySaverEnabled;
}
// Sends an error callback to all models with a valid registered callback.
diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/java/android/telephony/LocationAccessPolicy.java
new file mode 100644
index 0000000..b362df9
--- /dev/null
+++ b/telephony/java/android/telephony/LocationAccessPolicy.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.SparseBooleanArray;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper for performing location access checks.
+ * @hide
+ */
+public final class LocationAccessPolicy {
+ /**
+ * API to determine if the caller has permissions to get cell location.
+ *
+ * @param pkgName Package name of the application requesting access
+ * @param uid The uid of the package
+ * @param pid The pid of the package
+ * @return boolean true or false if permissions is granted
+ */
+ public static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName,
+ int uid, int pid) throws SecurityException {
+ Trace.beginSection("TelephonyLocationCheck");
+ try {
+ // Always allow the phone process to access location. This avoid breaking legacy code
+ // that rely on public-facing APIs to access cell location, and it doesn't create a
+ // info leak risk because the cell location is stored in the phone process anyway.
+ if (uid == Process.PHONE_UID) {
+ return true;
+ }
+
+ // We always require the location permission and also require the
+ // location mode to be on for non-legacy apps. Legacy apps are
+ // required to be in the foreground to at least mitigate the case
+ // where a legacy app the user is not using tracks their location.
+ // Granting ACCESS_FINE_LOCATION to an app automatically grants it
+ // ACCESS_COARSE_LOCATION.
+
+ if (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, pid, uid) ==
+ PackageManager.PERMISSION_DENIED) {
+ return false;
+ }
+ final int opCode = AppOpsManager.permissionToOpCode(
+ Manifest.permission.ACCESS_COARSE_LOCATION);
+ if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class)
+ .noteOpNoThrow(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))
+ && !isLegacyForeground(context, pkgName, uid)) {
+ return false;
+ }
+ // If the user or profile is current, permission is granted.
+ // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
+ return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) {
+ int locationMode = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, userId);
+ return locationMode != Settings.Secure.LOCATION_MODE_OFF
+ && locationMode != Settings.Secure.LOCATION_MODE_SENSORS_ONLY;
+ }
+
+ private static boolean isLegacyForeground(@NonNull Context context, @NonNull String pkgName,
+ int uid) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ return isLegacyVersion(context, pkgName) && isForegroundApp(context, uid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private static boolean isLegacyVersion(@NonNull Context context, @NonNull String pkgName) {
+ try {
+ if (context.getPackageManager().getApplicationInfo(pkgName, 0)
+ .targetSdkVersion <= Build.VERSION_CODES.O) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // In case of exception, assume known app (more strict checking)
+ // Note: This case will never happen since checkPackage is
+ // called to verify validity before checking app's version.
+ }
+ return false;
+ }
+
+ private static boolean isForegroundApp(@NonNull Context context, int uid) {
+ final ActivityManager am = context.getSystemService(ActivityManager.class);
+ return am.getUidImportance(uid) <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+ }
+
+ private static boolean checkInteractAcrossUsersFull(@NonNull Context context) {
+ return context.checkCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private static boolean isCurrentProfile(@NonNull Context context, int uid) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ final int currentUser = ActivityManager.getCurrentUser();
+ final int callingUserId = UserHandle.getUserId(uid);
+ if (callingUserId == currentUser) {
+ return true;
+ } else {
+ List<UserInfo> userProfiles = context.getSystemService(
+ UserManager.class).getProfiles(currentUser);
+ for (UserInfo user : userProfiles) {
+ if (user.id == callingUserId) {
+ return true;
+ }
+ }
+ }
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
index da3d87b..ce1b80c 100644
--- a/telephony/java/android/telephony/MbmsDownloadSession.java
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -30,7 +30,6 @@
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.telephony.mbms.DownloadStateCallback;
import android.telephony.mbms.FileInfo;
@@ -53,6 +52,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -107,11 +107,8 @@
/**
* {@link Uri} extra that Android will attach to the intent supplied via
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
- * Indicates the location of the successfully downloaded file within the temp file root set
- * via {@link #setTempFileRootDirectory(File)}.
- * While you may use this file in-place, it is highly encouraged that you move
- * this file to a different location after receiving the download completion intent, as this
- * file resides within the temp file directory.
+ * Indicates the location of the successfully downloaded file within the directory that the
+ * app provided via the builder.
*
* Will always be set to a non-null value if
* {@link #EXTRA_MBMS_DOWNLOAD_RESULT} is set to {@link #RESULT_SUCCESSFUL}.
@@ -220,6 +217,8 @@
*/
public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
+ private static final String DESTINATION_SANITY_CHECK_FILE_NAME = "destinationSanityCheckFile";
+
private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
private final Context mContext;
@@ -236,23 +235,20 @@
private final Map<DownloadStateCallback, InternalDownloadStateCallback>
mInternalDownloadCallbacks = new HashMap<>();
- private MbmsDownloadSession(Context context, MbmsDownloadSessionCallback callback,
- int subscriptionId, Handler handler) {
+ private MbmsDownloadSession(Context context, Executor executor, int subscriptionId,
+ MbmsDownloadSessionCallback callback) {
mContext = context;
mSubscriptionId = subscriptionId;
- if (handler == null) {
- handler = new Handler(Looper.getMainLooper());
- }
- mInternalCallback = new InternalDownloadSessionCallback(callback, handler);
+ mInternalCallback = new InternalDownloadSessionCallback(callback, executor);
}
/**
* Create a new {@link MbmsDownloadSession} using the system default data subscription ID.
- * See {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)}
+ * See {@link #create(Context, Executor, int, MbmsDownloadSessionCallback)}
*/
public static MbmsDownloadSession create(@NonNull Context context,
- @NonNull MbmsDownloadSessionCallback callback, @NonNull Handler handler) {
- return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
+ @NonNull Executor executor, @NonNull MbmsDownloadSessionCallback callback) {
+ return create(context, executor, SubscriptionManager.getDefaultSubscriptionId(), callback);
}
/**
@@ -279,24 +275,24 @@
* {@link MbmsDownloadSession} that you received before calling this method again.
*
* @param context The instance of {@link Context} to use
- * @param callback A callback to get asynchronous error messages and file service updates.
+ * @param executor The executor on which you wish to execute callbacks.
* @param subscriptionId The data subscription ID to use
- * @param handler The {@link Handler} on which callbacks should be enqueued.
+ * @param callback A callback to get asynchronous error messages and file service updates.
* @return A new instance of {@link MbmsDownloadSession}, or null if an error occurred during
* setup.
*/
public static @Nullable MbmsDownloadSession create(@NonNull Context context,
- final @NonNull MbmsDownloadSessionCallback callback,
- int subscriptionId, @NonNull Handler handler) {
+ @NonNull Executor executor, int subscriptionId,
+ final @NonNull MbmsDownloadSessionCallback callback) {
if (!sIsInitialized.compareAndSet(false, true)) {
throw new IllegalStateException("Cannot have two active instances");
}
MbmsDownloadSession session =
- new MbmsDownloadSession(context, callback, subscriptionId, handler);
+ new MbmsDownloadSession(context, executor, subscriptionId, callback);
final int result = session.bindAndInitialize();
if (result != MbmsErrors.SUCCESS) {
sIsInitialized.set(false);
- handler.post(new Runnable() {
+ executor.execute(new Runnable() {
@Override
public void run() {
callback.onError(result, null);
@@ -500,6 +496,10 @@
* {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
* file root directory.
*
+ * If the {@link DownloadRequest} has a destination that is not on the same filesystem as the
+ * temp file directory provided via {@link #getTempFileRootDirectory()}, an
+ * {@link IllegalArgumentException} will be thrown.
+ *
* Asynchronous errors through the callback may include any error not specific to the
* streaming use-case.
* @param request The request that specifies what should be downloaded.
@@ -522,6 +522,8 @@
setTempFileRootDirectory(tempRootDirectory);
}
+ checkDownloadRequestDestination(request);
+
try {
int result = downloadService.download(request);
if (result == MbmsErrors.SUCCESS) {
@@ -568,21 +570,21 @@
* this method will throw an {@link IllegalArgumentException}.
*
* @param request The {@link DownloadRequest} that you want updates on.
+ * @param executor The {@link Executor} on which calls to {@code callback} should be executed.
* @param callback The callback that should be called when the middleware has information to
* share on the download.
- * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
* @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error,
* and some other error code otherwise.
*/
public int registerStateCallback(@NonNull DownloadRequest request,
- @NonNull DownloadStateCallback callback, @NonNull Handler handler) {
+ @NonNull Executor executor, @NonNull DownloadStateCallback callback) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new IllegalStateException("Middleware not yet bound");
}
InternalDownloadStateCallback internalCallback =
- new InternalDownloadStateCallback(callback, handler);
+ new InternalDownloadStateCallback(callback, executor);
try {
int result = downloadService.registerStateCallback(request, internalCallback,
@@ -604,7 +606,7 @@
/**
* Un-register a callback previously registered via
- * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback, Handler)}. After
+ * {@link #registerStateCallback(DownloadRequest, Executor, DownloadStateCallback)}. After
* this method is called, no further callbacks will be enqueued on the {@link Handler}
* provided upon registration, even if this method throws an exception.
*
@@ -692,7 +694,7 @@
* The state will be delivered as a callback via
* {@link DownloadStateCallback#onStateUpdated(DownloadRequest, FileInfo, int)}. If no such
* callback has been registered via
- * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback, Handler)}, this
+ * {@link #registerStateCallback(DownloadRequest, Executor, DownloadStateCallback)}, this
* method will be a no-op.
*
* If the middleware has no record of the
@@ -775,7 +777,7 @@
* instance of {@link MbmsDownloadSessionCallback}, but callbacks that have already been
* enqueued will still be delivered.
*
- * It is safe to call {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)} to
+ * It is safe to call {@link #create(Context, Executor, int, MbmsDownloadSessionCallback)} to
* obtain another instance of {@link MbmsDownloadSession} immediately after this method
* returns.
*
@@ -831,6 +833,36 @@
}
}
+ private void checkDownloadRequestDestination(DownloadRequest request) {
+ File downloadRequestDestination = new File(request.getDestinationUri().getPath());
+ if (!downloadRequestDestination.isDirectory()) {
+ throw new IllegalArgumentException("The destination path must be a directory");
+ }
+ // Check if the request destination is okay to use by attempting to rename an empty
+ // file to there.
+ File testFile = new File(MbmsTempFileProvider.getEmbmsTempFileDir(mContext),
+ DESTINATION_SANITY_CHECK_FILE_NAME);
+ File testFileDestination = new File(downloadRequestDestination,
+ DESTINATION_SANITY_CHECK_FILE_NAME);
+
+ try {
+ if (!testFile.exists()) {
+ testFile.createNewFile();
+ }
+ if (!testFile.renameTo(testFileDestination)) {
+ throw new IllegalArgumentException("Destination provided in the download request " +
+ "is invalid -- files in the temp file directory cannot be directly moved " +
+ "there.");
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Got IOException while testing out the destination: "
+ + e);
+ } finally {
+ testFile.delete();
+ testFileDestination.delete();
+ }
+ }
+
private File getDownloadRequestTokenPath(DownloadRequest request) {
File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
request.getFileServiceId());
diff --git a/telephony/java/android/telephony/MbmsStreamingSession.java b/telephony/java/android/telephony/MbmsStreamingSession.java
index fb2ff7b..42c760d4 100644
--- a/telephony/java/android/telephony/MbmsStreamingSession.java
+++ b/telephony/java/android/telephony/MbmsStreamingSession.java
@@ -24,9 +24,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.telephony.mbms.InternalStreamingSessionCallback;
import android.telephony.mbms.InternalStreamingServiceCallback;
@@ -42,6 +40,7 @@
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -89,14 +88,11 @@
private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
/** @hide */
- private MbmsStreamingSession(Context context, MbmsStreamingSessionCallback callback,
- int subscriptionId, Handler handler) {
+ private MbmsStreamingSession(Context context, Executor executor, int subscriptionId,
+ MbmsStreamingSessionCallback callback) {
mContext = context;
mSubscriptionId = subscriptionId;
- if (handler == null) {
- handler = new Handler(Looper.getMainLooper());
- }
- mInternalCallback = new InternalStreamingSessionCallback(callback, handler);
+ mInternalCallback = new InternalStreamingSessionCallback(callback, executor);
}
/**
@@ -117,25 +113,25 @@
* {@link MbmsStreamingSession} that you received before calling this method again.
*
* @param context The {@link Context} to use.
+ * @param executor The executor on which you wish to execute callbacks.
+ * @param subscriptionId The subscription ID to use.
* @param callback A callback object on which you wish to receive results of asynchronous
* operations.
- * @param subscriptionId The subscription ID to use.
- * @param handler The handler you wish to receive callbacks on.
* @return An instance of {@link MbmsStreamingSession}, or null if an error occurred.
*/
public static @Nullable MbmsStreamingSession create(@NonNull Context context,
- final @NonNull MbmsStreamingSessionCallback callback, int subscriptionId,
- @NonNull Handler handler) {
+ @NonNull Executor executor, int subscriptionId,
+ final @NonNull MbmsStreamingSessionCallback callback) {
if (!sIsInitialized.compareAndSet(false, true)) {
throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession");
}
- MbmsStreamingSession session = new MbmsStreamingSession(context, callback,
- subscriptionId, handler);
+ MbmsStreamingSession session = new MbmsStreamingSession(context, executor,
+ subscriptionId, callback);
final int result = session.bindAndInitialize();
if (result != MbmsErrors.SUCCESS) {
sIsInitialized.set(false);
- handler.post(new Runnable() {
+ executor.execute(new Runnable() {
@Override
public void run() {
callback.onError(result, null);
@@ -148,22 +144,22 @@
/**
* Create a new {@link MbmsStreamingSession} using the system default data subscription ID.
- * See {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)}.
+ * See {@link #create(Context, Executor, int, MbmsStreamingSessionCallback)}.
*/
public static MbmsStreamingSession create(@NonNull Context context,
- @NonNull MbmsStreamingSessionCallback callback, @NonNull Handler handler) {
- return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
+ @NonNull Executor executor, @NonNull MbmsStreamingSessionCallback callback) {
+ return create(context, executor, SubscriptionManager.getDefaultSubscriptionId(), callback);
}
/**
* Terminates this instance. Also terminates
* any streaming services spawned from this instance as if
- * {@link StreamingService#stopStreaming()} had been called on them. After this method returns,
+ * {@link StreamingService#close()} had been called on them. After this method returns,
* no further callbacks originating from the middleware will be enqueued on the provided
* instance of {@link MbmsStreamingSessionCallback}, but callbacks that have already been
* enqueued will still be delivered.
*
- * It is safe to call {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)} to
+ * It is safe to call {@link #create(Context, Executor, int, MbmsStreamingSessionCallback)} to
* obtain another instance of {@link MbmsStreamingSession} immediately after this method
* returns.
*
@@ -237,20 +233,20 @@
* {@link MbmsErrors.StreamingErrors}.
*
* @param serviceInfo The information about the service to stream.
+ * @param executor The executor on which you wish to execute callbacks for this stream.
* @param callback A callback that'll be called when something about the stream changes.
- * @param handler A handler that calls to {@code callback} should be called on.
* @return An instance of {@link StreamingService} through which the stream can be controlled.
* May be {@code null} if an error occurred.
*/
public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo,
- StreamingServiceCallback callback, @NonNull Handler handler) {
+ @NonNull Executor executor, StreamingServiceCallback callback) {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
throw new IllegalStateException("Middleware not yet bound");
}
InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
- callback, handler);
+ callback, executor);
StreamingService serviceForApp = new StreamingService(
mSubscriptionId, streamingService, this, serviceInfo, serviceCallback);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 11a1984..4a61437 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -613,9 +613,9 @@
* onSubscriptionsChanged overridden.
*/
public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
- String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+ String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>";
if (DBG) {
- logd("register OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug
+ logd("register OnSubscriptionsChangedListener pkgName=" + pkgName
+ " listener=" + listener);
}
try {
@@ -624,7 +624,7 @@
ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
if (tr != null) {
- tr.addOnSubscriptionsChangedListener(pkgForDebug, listener.callback);
+ tr.addOnSubscriptionsChangedListener(pkgName, listener.callback);
}
} catch (RemoteException ex) {
// Should not happen
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fefc03d..cdc1ba9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4054,6 +4054,9 @@
* To unregister a listener, pass the listener object and set the
* events argument to
* {@link PhoneStateListener#LISTEN_NONE LISTEN_NONE} (0).
+ * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
+ * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
+ * {@link SecurityException} will be thrown otherwise.
*
* @param listener The {@link PhoneStateListener} object to register
* (or unregister)
@@ -4846,8 +4849,6 @@
return;
}
- Rlog.d(TAG, "setTelephonyProperty: success phoneId=" + phoneId +
- " property=" + property + " value: " + value + " propVal=" + propVal);
SystemProperties.set(property, propVal);
}
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index f0d60b6..602c796 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -27,10 +27,13 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.Externalizable;
+import java.io.File;
import java.io.IOException;
+import java.io.ObjectInput;
import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
-import java.io.Serializable;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -54,34 +57,116 @@
public static final int MAX_DESTINATION_URI_SIZE = 50000;
/** @hide */
- private static class OpaqueDataContainer implements Serializable {
- private final String appIntent;
- private final int version;
+ private static class SerializationDataContainer implements Externalizable {
+ private String fileServiceId;
+ private Uri source;
+ private Uri destination;
+ private int subscriptionId;
+ private String appIntent;
+ private int version;
- public OpaqueDataContainer(String appIntent, int version) {
- this.appIntent = appIntent;
- this.version = version;
+ public SerializationDataContainer() {}
+
+ SerializationDataContainer(DownloadRequest request) {
+ fileServiceId = request.fileServiceId;
+ source = request.sourceUri;
+ destination = request.destinationUri;
+ subscriptionId = request.subscriptionId;
+ appIntent = request.serializedResultIntentForApp;
+ version = request.version;
+ }
+
+ @Override
+ public void writeExternal(ObjectOutput objectOutput) throws IOException {
+ objectOutput.write(version);
+ objectOutput.writeUTF(fileServiceId);
+ objectOutput.writeUTF(source.toString());
+ objectOutput.writeUTF(destination.toString());
+ objectOutput.write(subscriptionId);
+ objectOutput.writeUTF(appIntent);
+ }
+
+ @Override
+ public void readExternal(ObjectInput objectInput) throws IOException {
+ version = objectInput.read();
+ fileServiceId = objectInput.readUTF();
+ source = Uri.parse(objectInput.readUTF());
+ destination = Uri.parse(objectInput.readUTF());
+ subscriptionId = objectInput.read();
+ appIntent = objectInput.readUTF();
+ // Do version checks here -- future versions may have other fields.
}
}
public static class Builder {
private String fileServiceId;
private Uri source;
+ private Uri destination;
private int subscriptionId;
private String appIntent;
private int version = CURRENT_VERSION;
+ /**
+ * Constructs a {@link Builder} from a {@link DownloadRequest}
+ * @param other The {@link DownloadRequest} from which the data for the {@link Builder}
+ * should come.
+ * @return An instance of {@link Builder} pre-populated with data from the provided
+ * {@link DownloadRequest}.
+ */
+ public static Builder fromDownloadRequest(DownloadRequest other) {
+ Builder result = new Builder(other.sourceUri, other.destinationUri)
+ .setServiceId(other.fileServiceId)
+ .setSubscriptionId(other.subscriptionId);
+ result.appIntent = other.serializedResultIntentForApp;
+ // Version of the result is going to be the current version -- as this class gets
+ // updated, new fields will be set to default values in here.
+ return result;
+ }
+
+ /**
+ * This method constructs a new instance of {@link Builder} based on the serialized data
+ * passed in.
+ * @param data A byte array, the contents of which should have been originally obtained
+ * from {@link DownloadRequest#toByteArray()}.
+ */
+ public static Builder fromSerializedRequest(byte[] data) {
+ Builder builder;
+ try {
+ ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data));
+ SerializationDataContainer dataContainer =
+ (SerializationDataContainer) stream.readObject();
+ builder = new Builder(dataContainer.source, dataContainer.destination);
+ builder.version = dataContainer.version;
+ builder.appIntent = dataContainer.appIntent;
+ builder.fileServiceId = dataContainer.fileServiceId;
+ builder.subscriptionId = dataContainer.subscriptionId;
+ } catch (IOException e) {
+ // Really should never happen
+ Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
+ throw new IllegalArgumentException(e);
+ } catch (ClassNotFoundException e) {
+ Log.e(LOG_TAG, "Got ClassNotFoundException trying to parse opaque data");
+ throw new IllegalArgumentException(e);
+ }
+ return builder;
+ }
/**
* Builds a new DownloadRequest.
* @param sourceUri the source URI for the DownloadRequest to be built. This URI should
* never be null.
+ * @param destinationUri The final location for the file(s) that are to be downloaded. It
+ * must be on the same filesystem as the temp file directory set via
+ * {@link android.telephony.MbmsDownloadSession#setTempFileRootDirectory(File)}.
+ * The provided path must be a directory that exists. An
+ * {@link IllegalArgumentException} will be thrown otherwise.
*/
- public Builder(@NonNull Uri sourceUri) {
- if (sourceUri == null) {
- throw new IllegalArgumentException("Source URI must be non-null.");
+ public Builder(@NonNull Uri sourceUri, @NonNull Uri destinationUri) {
+ if (sourceUri == null || destinationUri == null) {
+ throw new IllegalArgumentException("Source and destination URIs must be non-null.");
}
source = sourceUri;
+ destination = destinationUri;
}
/**
@@ -130,68 +215,34 @@
return this;
}
- /**
- * For use by the middleware to set the byte array of opaque data. The opaque data
- * includes information about the download request that is used by the client app and the
- * manager code, but is irrelevant to the middleware.
- * @param data A byte array, the contents of which should have been originally obtained
- * from {@link DownloadRequest#getOpaqueData()}.
- * @hide
- */
- @SystemApi
- public Builder setOpaqueData(byte[] data) {
- try {
- ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data));
- OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();
- version = dataContainer.version;
- appIntent = dataContainer.appIntent;
- } catch (IOException e) {
- // Really should never happen
- Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
- throw new IllegalArgumentException(e);
- } catch (ClassNotFoundException e) {
- Log.e(LOG_TAG, "Got ClassNotFoundException trying to parse opaque data");
- throw new IllegalArgumentException(e);
- }
- return this;
- }
-
public DownloadRequest build() {
- return new DownloadRequest(fileServiceId, source, subscriptionId, appIntent, version);
+ return new DownloadRequest(fileServiceId, source, destination,
+ subscriptionId, appIntent, version);
}
}
private final String fileServiceId;
private final Uri sourceUri;
+ private final Uri destinationUri;
private final int subscriptionId;
private final String serializedResultIntentForApp;
private final int version;
private DownloadRequest(String fileServiceId,
- Uri source, int sub,
+ Uri source, Uri destination, int sub,
String appIntent, int version) {
this.fileServiceId = fileServiceId;
sourceUri = source;
subscriptionId = sub;
+ destinationUri = destination;
serializedResultIntentForApp = appIntent;
this.version = version;
}
- public static DownloadRequest copy(DownloadRequest other) {
- return new DownloadRequest(other);
- }
-
- private DownloadRequest(DownloadRequest dr) {
- fileServiceId = dr.fileServiceId;
- sourceUri = dr.sourceUri;
- subscriptionId = dr.subscriptionId;
- serializedResultIntentForApp = dr.serializedResultIntentForApp;
- version = dr.version;
- }
-
private DownloadRequest(Parcel in) {
fileServiceId = in.readString();
sourceUri = in.readParcelable(getClass().getClassLoader());
+ destinationUri = in.readParcelable(getClass().getClassLoader());
subscriptionId = in.readInt();
serializedResultIntentForApp = in.readString();
version = in.readInt();
@@ -204,6 +255,7 @@
public void writeToParcel(Parcel out, int flags) {
out.writeString(fileServiceId);
out.writeParcelable(sourceUri, flags);
+ out.writeParcelable(destinationUri, flags);
out.writeInt(subscriptionId);
out.writeString(serializedResultIntentForApp);
out.writeInt(version);
@@ -224,6 +276,13 @@
}
/**
+ * @return The destination {@link Uri} of the downloaded file.
+ */
+ public Uri getDestinationUri() {
+ return destinationUri;
+ }
+
+ /**
* @return The subscription ID on which to perform MBMS operations.
*/
public int getSubscriptionId() {
@@ -244,19 +303,16 @@
}
/**
- * For use by the middleware only. The byte array returned from this method should be
- * persisted and sent back to the app upon download completion or failure by passing it into
- * {@link Builder#setOpaqueData(byte[])}.
- * @return A byte array of opaque data to persist.
- * @hide
+ * This method returns a byte array that may be persisted to disk and restored to a
+ * {@link DownloadRequest}. The instance of {@link DownloadRequest} persisted by this method
+ * may be recovered via {@link Builder#fromSerializedRequest(byte[])}.
+ * @return A byte array of data to persist.
*/
- @SystemApi
- public byte[] getOpaqueData() {
+ public byte[] toByteArray() {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
- OpaqueDataContainer container = new OpaqueDataContainer(
- serializedResultIntentForApp, version);
+ SerializationDataContainer container = new SerializationDataContainer(this);
stream.writeObject(container);
stream.flush();
return byteArrayOutputStream.toByteArray();
@@ -299,15 +355,6 @@
}
/**
- * @hide
- */
- public boolean isMultipartDownload() {
- // TODO: figure out what qualifies a request as a multipart download request.
- return getSourceUri().getLastPathSegment() != null &&
- getSourceUri().getLastPathSegment().contains("*");
- }
-
- /**
* Retrieves the hash string that should be used as the filename when storing a token for
* this DownloadRequest.
* @hide
@@ -320,8 +367,9 @@
throw new RuntimeException("Could not get sha256 hash object");
}
if (version >= 1) {
- // Hash the source URI and the app intent
+ // Hash the source, destination, and the app intent
digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
+ digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
if (serializedResultIntentForApp != null) {
digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
}
@@ -344,12 +392,13 @@
version == request.version &&
Objects.equals(fileServiceId, request.fileServiceId) &&
Objects.equals(sourceUri, request.sourceUri) &&
+ Objects.equals(destinationUri, request.destinationUri) &&
Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);
}
@Override
public int hashCode() {
- return Objects.hash(fileServiceId, sourceUri,
+ return Objects.hash(fileServiceId, sourceUri, destinationUri,
subscriptionId, serializedResultIntentForApp, version);
}
}
diff --git a/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java
index a7a5958..c2a79d8 100644
--- a/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java
@@ -16,22 +16,23 @@
package android.telephony.mbms;
-import android.os.Handler;
+import android.os.Binder;
import android.os.RemoteException;
import java.util.List;
+import java.util.concurrent.Executor;
/** @hide */
public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallback.Stub {
- private final Handler mHandler;
+ private final Executor mExecutor;
private final MbmsDownloadSessionCallback mAppCallback;
private volatile boolean mIsStopped = false;
public InternalDownloadSessionCallback(MbmsDownloadSessionCallback appCallback,
- Handler handler) {
+ Executor executor) {
mAppCallback = appCallback;
- mHandler = handler;
+ mExecutor = executor;
}
@Override
@@ -40,10 +41,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onError(errorCode, message);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onError(errorCode, message);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
@@ -54,10 +60,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onFileServicesUpdated(services);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onFileServicesUpdated(services);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
@@ -68,18 +79,19 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onMiddlewareReady();
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onMiddlewareReady();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
- public Handler getHandler() {
- return mHandler;
- }
-
public void stop() {
mIsStopped = true;
}
diff --git a/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java
index 8702952..f30ae27 100644
--- a/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java
@@ -16,20 +16,22 @@
package android.telephony.mbms;
-import android.os.Handler;
+import android.os.Binder;
import android.os.RemoteException;
+import java.util.concurrent.Executor;
+
/**
* @hide
*/
public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {
- private final Handler mHandler;
+ private final Executor mExecutor;
private final DownloadStateCallback mAppCallback;
private volatile boolean mIsStopped = false;
- public InternalDownloadStateCallback(DownloadStateCallback appCallback, Handler handler) {
+ public InternalDownloadStateCallback(DownloadStateCallback appCallback, Executor executor) {
mAppCallback = appCallback;
- mHandler = handler;
+ mExecutor = executor;
}
@Override
@@ -40,11 +42,16 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onProgressUpdated(request, fileInfo, currentDownloadSize,
- fullDownloadSize, currentDecodedSize, fullDecodedSize);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+ fullDownloadSize, currentDecodedSize, fullDecodedSize);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
@@ -56,10 +63,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onStateUpdated(request, fileInfo, state);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onStateUpdated(request, fileInfo, state);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
index eb6579ce..e9f39ff 100644
--- a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
@@ -16,18 +16,21 @@
package android.telephony.mbms;
-import android.os.Handler;
+import android.os.Binder;
import android.os.RemoteException;
+import java.util.concurrent.Executor;
+
/** @hide */
public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub {
private final StreamingServiceCallback mAppCallback;
- private final Handler mHandler;
+ private final Executor mExecutor;
private volatile boolean mIsStopped = false;
- public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) {
+ public InternalStreamingServiceCallback(StreamingServiceCallback appCallback,
+ Executor executor) {
mAppCallback = appCallback;
- mHandler = handler;
+ mExecutor = executor;
}
@Override
@@ -36,10 +39,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onError(errorCode, message);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onError(errorCode, message);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
@@ -50,10 +58,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onStreamStateUpdated(state, reason);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onStreamStateUpdated(state, reason);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
@@ -64,10 +77,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onMediaDescriptionUpdated();
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onMediaDescriptionUpdated();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
@@ -78,10 +96,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onBroadcastSignalStrengthUpdated(signalStrength);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onBroadcastSignalStrengthUpdated(signalStrength);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
@@ -92,10 +115,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onStreamMethodUpdated(methodType);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onStreamMethodUpdated(methodType);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
index d782d12..d47f5ad 100644
--- a/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
@@ -16,21 +16,22 @@
package android.telephony.mbms;
-import android.os.Handler;
+import android.os.Binder;
import android.os.RemoteException;
import java.util.List;
+import java.util.concurrent.Executor;
/** @hide */
public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallback.Stub {
- private final Handler mHandler;
+ private final Executor mExecutor;
private final MbmsStreamingSessionCallback mAppCallback;
private volatile boolean mIsStopped = false;
public InternalStreamingSessionCallback(MbmsStreamingSessionCallback appCallback,
- Handler handler) {
+ Executor executor) {
mAppCallback = appCallback;
- mHandler = handler;
+ mExecutor = executor;
}
@Override
@@ -39,10 +40,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onError(errorCode, message);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onError(errorCode, message);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
@@ -54,10 +60,15 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onStreamingServicesUpdated(services);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onStreamingServicesUpdated(services);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
@@ -68,18 +79,19 @@
return;
}
- mHandler.post(new Runnable() {
+ mExecutor.execute(new Runnable() {
@Override
public void run() {
- mAppCallback.onMiddlewareReady();
+ long token = Binder.clearCallingIdentity();
+ try {
+ mAppCallback.onMiddlewareReady();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
});
}
- public Handler getHandler() {
- return mHandler;
- }
-
public void stop() {
mIsStopped = true;
}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index 9ef188c..b0c00c6 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -21,8 +21,10 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.MbmsDownloadSession;
@@ -31,14 +33,11 @@
import java.io.File;
import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -62,6 +61,8 @@
/** @hide */
public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
+ private static final String EMBMS_INTENT_PERMISSION = "android.permission.SEND_EMBMS_INTENTS";
+
/**
* Indicates that the requested operation completed without error.
* @hide
@@ -137,6 +138,8 @@
/** @hide */
@Override
public void onReceive(Context context, Intent intent) {
+ verifyPermissionIntegrity(context);
+
if (!verifyIntentContents(context, intent)) {
setResultCode(RESULT_MALFORMED_INTENT);
return;
@@ -260,20 +263,18 @@
FileInfo completedFileInfo =
(FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO);
- Path stagingDirectory = FileSystems.getDefault().getPath(
- MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath(),
- TEMP_FILE_STAGING_LOCATION);
+ Path appSpecifiedDestination = FileSystems.getDefault().getPath(
+ request.getDestinationUri().getPath());
- Uri stagedFileLocation;
+ Uri finalLocation;
try {
- stagedFileLocation = stageTempFile(finalTempFile, stagingDirectory);
+ finalLocation = moveToFinalLocation(finalTempFile, appSpecifiedDestination);
} catch (IOException e) {
Log.w(LOG_TAG, "Failed to move temp file to final destination");
setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
return;
}
- intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI,
- stagedFileLocation);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI, finalLocation);
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo);
context.sendBroadcast(intentForApp);
@@ -437,19 +438,22 @@
}
/*
- * Moves a tempfile located at fromPath to a new location in the staging directory.
+ * Moves a tempfile located at fromPath to its final home where the app wants it
*/
- private static Uri stageTempFile(Uri fromPath, Path stagingDirectory) throws IOException {
+ private static Uri moveToFinalLocation(Uri fromPath, Path appSpecifiedPath) throws IOException {
if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {
- Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme");
+ Log.w(LOG_TAG, "Downloaded file location uri " + fromPath +
+ " does not have a file scheme");
return null;
}
Path fromFile = FileSystems.getDefault().getPath(fromPath.getPath());
- if (!Files.isDirectory(stagingDirectory)) {
- Files.createDirectory(stagingDirectory);
+ if (!Files.isDirectory(appSpecifiedPath)) {
+ Files.createDirectory(appSpecifiedPath);
}
- Path result = Files.move(fromFile, stagingDirectory.resolve(fromFile.getFileName()));
+ // TODO: do we want to support directory trees within the download directory?
+ Path result = Files.move(fromFile, appSpecifiedPath.resolve(fromFile.getFileName()),
+ StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
return Uri.fromFile(result.toFile());
}
@@ -513,39 +517,29 @@
return mMiddlewarePackageNameCache;
}
- private static boolean manualMove(File src, File dst) {
- InputStream in = null;
- OutputStream out = null;
- try {
- if (!dst.exists()) {
- dst.createNewFile();
- }
- in = new FileInputStream(src);
- out = new FileOutputStream(dst);
- byte[] buffer = new byte[2048];
- int len;
- do {
- len = in.read(buffer);
- out.write(buffer, 0, len);
- } while (len > 0);
- } catch (IOException e) {
- Log.w(LOG_TAG, "Manual file move failed due to exception " + e);
- if (dst.exists()) {
- dst.delete();
- }
- return false;
- } finally {
- try {
- if (in != null) {
- in.close();
- }
- if (out != null) {
- out.close();
- }
- } catch (IOException e) {
- Log.w(LOG_TAG, "Error closing streams: " + e);
- }
+ private void verifyPermissionIntegrity(Context context) {
+ PackageManager pm = context.getPackageManager();
+ Intent queryIntent = new Intent(context, MbmsDownloadReceiver.class);
+ List<ResolveInfo> infos = pm.queryBroadcastReceivers(queryIntent, 0);
+ if (infos.size() != 1) {
+ throw new IllegalStateException("Non-unique download receiver in your app");
}
- return true;
+ ActivityInfo selfInfo = infos.get(0).activityInfo;
+ if (selfInfo == null) {
+ throw new IllegalStateException("Queried ResolveInfo does not contain a receiver");
+ }
+ if (MbmsUtils.getOverrideServiceName(context,
+ MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION) != null) {
+ // If an override was specified, just make sure that the permission isn't null.
+ if (selfInfo.permission == null) {
+ throw new IllegalStateException(
+ "MbmsDownloadReceiver must require some permission");
+ }
+ return;
+ }
+ if (!Objects.equals(EMBMS_INTENT_PERMISSION, selfInfo.permission)) {
+ throw new IllegalStateException("MbmsDownloadReceiver must require the " +
+ "SEND_EMBMS_INTENTS permission.");
+ }
}
}
diff --git a/telephony/java/android/telephony/mbms/MbmsErrors.java b/telephony/java/android/telephony/mbms/MbmsErrors.java
index 75ca35e..b5fec44 100644
--- a/telephony/java/android/telephony/mbms/MbmsErrors.java
+++ b/telephony/java/android/telephony/mbms/MbmsErrors.java
@@ -108,8 +108,8 @@
/**
* Indicates that the app called
- * {@link MbmsStreamingSession#startStreaming(
- * StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)}
+ * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
+ * java.util.concurrent.Executor, StreamingServiceCallback)}
* more than once for the same {@link StreamingServiceInfo}.
*/
public static final int ERROR_DUPLICATE_START_STREAM = 303;
diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java
index 5c130a0..6e03957 100644
--- a/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java
+++ b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java
@@ -22,11 +22,12 @@
import android.telephony.MbmsStreamingSession;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* A callback class that is used to receive information from the middleware on MBMS streaming
* services. An instance of this object should be passed into
- * {@link MbmsStreamingSession#create(Context, MbmsStreamingSessionCallback, int, Handler)}.
+ * {@link MbmsStreamingSession#create(Context, Executor, int, MbmsStreamingSessionCallback)}.
*/
public class MbmsStreamingSessionCallback {
/**
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index b4ad1d7..ef317ee 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -50,7 +50,7 @@
return new ComponentName(ci.packageName, ci.name);
}
- private static ComponentName getOverrideServiceName(Context context, String serviceAction) {
+ public static ComponentName getOverrideServiceName(Context context, String serviceAction) {
String metaDataKey = null;
switch (serviceAction) {
case MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION:
diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java
index ec9134a..b6239fe 100644
--- a/telephony/java/android/telephony/mbms/StreamingService.java
+++ b/telephony/java/android/telephony/mbms/StreamingService.java
@@ -29,11 +29,11 @@
/**
* Class used to represent a single MBMS stream. After a stream has been started with
- * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
- * StreamingServiceCallback, android.os.Handler)},
+ * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo, java.util.concurrent.Executor,
+ * StreamingServiceCallback)},
* this class is used to hold information about the stream and control it.
*/
-public class StreamingService {
+public class StreamingService implements AutoCloseable {
private static final String LOG_TAG = "MbmsStreamingService";
/**
@@ -41,7 +41,7 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef({STATE_STOPPED, STATE_STARTED, STATE_STALLED})
+ @IntDef(prefix = { "STATE_" }, value = {STATE_STOPPED, STATE_STARTED, STATE_STALLED})
public @interface StreamingState {}
public final static int STATE_STOPPED = 1;
public final static int STATE_STARTED = 2;
@@ -53,7 +53,8 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef({REASON_BY_USER_REQUEST, REASON_END_OF_SESSION, REASON_FREQUENCY_CONFLICT,
+ @IntDef(prefix = { "REASON_" },
+ value = {REASON_BY_USER_REQUEST, REASON_END_OF_SESSION, REASON_FREQUENCY_CONFLICT,
REASON_OUT_OF_MEMORY, REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE,
REASON_LEFT_MBMS_BROADCAST_AREA, REASON_NONE})
public @interface StreamingStateChangeReason {}
@@ -64,9 +65,9 @@
public static final int REASON_NONE = 0;
/**
- * State changed due to a call to {@link #stopStreaming()} or
+ * State changed due to a call to {@link #close()} or
* {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
- * StreamingServiceCallback, android.os.Handler)}
+ * java.util.concurrent.Executor, StreamingServiceCallback)}
*/
public static final int REASON_BY_USER_REQUEST = 1;
@@ -161,7 +162,8 @@
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*/
- public void stopStreaming() {
+ @Override
+ public void close() {
if (mService == null) {
throw new IllegalStateException("No streaming service attached");
}
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 4912224..4d149f7 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -31,12 +31,8 @@
// Needs to be consistent with the repackaged version of this make target.
java_version: "1.8",
- no_framework_libs: true,
+ sdk_version: "current",
hostdex: true,
- libs: [
- "framework",
- ],
-
}
// Build the legacy-test library
@@ -46,12 +42,9 @@
// Also contains the com.android.internal.util.Predicate[s] classes.
java_library {
name: "legacy-test",
- static_libs: ["android.test.base"],
- no_framework_libs: true,
- libs: [
- "framework",
- ],
+ sdk_version: "current",
+ static_libs: ["android.test.base"],
}
// Build the repackaged.android.test.base library
@@ -61,11 +54,8 @@
java_library_static {
name: "repackaged.android.test.base",
+ sdk_version: "current",
static_libs: ["android.test.base"],
- no_framework_libs: true,
- libs: [
- "framework",
- ],
jarjar_rules: "jarjar-rules.txt",
// Pin java_version until jarjar is certified to support later versions. http://b/72703434
diff --git a/test-base/Android.mk b/test-base/Android.mk
index 8613854..ebb33de 100644
--- a/test-base/Android.mk
+++ b/test-base/Android.mk
@@ -26,10 +26,7 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := \
- core-oj \
- core-libart \
- framework \
+LOCAL_SDK_VERSION := current
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src
diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk
index b8c5326..da47de0 100644
--- a/test-legacy/Android.mk
+++ b/test-legacy/Android.mk
@@ -21,16 +21,38 @@
# Build the android.test.legacy library
# =====================================
+# Built against the SDK so that it can be statically included in APKs
+# without breaking link type checks.
+#
+# This builds directly from the source rather than simply statically
+# including the android.test.base-minus-junit and
+# android.test.runner-minus-junit libraries because the latter library
+# cannot itself be built against the SDK. That is because it uses on
+# an internal method (setTestContext) on the AndroidTestCase class.
+# That class is provided by both the android.test.base-minus-junit and
+# the current SDK and as the latter is first on the classpath its
+# version is used. Unfortunately, it does not provide the internal
+# method and so compilation fails.
+#
+# Building from source avoids that because the compiler will use the
+# source version of AndroidTestCase instead of the one from the current
+# SDK.
+#
+# The use of the internal method does not prevent this from being
+# statically included because the class that provides the method is
+# also included in this library.
include $(CLEAR_VARS)
LOCAL_MODULE := android.test.legacy
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, ../test-base/src/android) \
+ $(call all-java-files-under, ../test-base/src/com) \
+ $(call all-java-files-under, ../test-runner/src/android) \
+
LOCAL_SDK_VERSION := current
-LOCAL_JAVA_LIBRARIES := junit
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android.test.base-minus-junit \
- android.test.runner-minus-junit \
+LOCAL_JAVA_LIBRARIES := junit android.test.mock.stubs
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/test-mock/Android.mk b/test-mock/Android.mk
index 5c586c7..de1b6c7 100644
--- a/test-mock/Android.mk
+++ b/test-mock/Android.mk
@@ -21,7 +21,7 @@
# otherwise hidden methods could be visible.
android_test_mock_source_files := \
$(call all-java-files-under, src/android/test/mock) \
- $(call all-java-files-under, ../core/java) \
+ $(call all-java-files-under, ../core/java/android)
# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
@@ -30,7 +30,6 @@
# ==========================================================
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(android_test_mock_source_files)
-
LOCAL_JAVA_LIBRARIES := core-oj core-libart framework conscrypt okhttp bouncycastle
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src/android/test/mock
@@ -42,6 +41,7 @@
ANDROID_TEST_MOCK_REMOVED_API_FILE := $(LOCAL_PATH)/api/android-test-mock-removed.txt
LOCAL_DROIDDOC_OPTIONS:= \
+ -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 \
-stubpackages android.test.mock \
-stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs_intermediates/src \
-nodocs \
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index 1ddc52c..1af7c3a 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -1209,4 +1209,11 @@
throw new UnsupportedOperationException();
}
+ /**
+ * @hide
+ */
+ @Override
+ public String getSystemTextClassifierPackageName() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index fb7c2a7..c69279b 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -27,9 +27,8 @@
javacflags: ["-Xep:DepAnn:ERROR"],
},
- no_framework_libs: true,
+ sdk_version: "current",
libs: [
- "framework",
"android.test.base",
"android.test.mock",
],
@@ -37,16 +36,15 @@
// Build the android.test.runner-minus-junit library
// =================================================
-// This is only intended for inclusion in the android.test.legacy and
-// legacy-android-test static libraries and must not be used elsewhere.
+// This is only intended for inclusion in the legacy-android-test static
+// library and must not be used elsewhere.
java_library {
name: "android.test.runner-minus-junit",
srcs: ["src/android/**/*.java"],
- no_framework_libs: true,
+ sdk_version: "current",
libs: [
- "framework",
"android.test.base",
"android.test.mock",
"junit",
@@ -58,6 +56,7 @@
java_library_static {
name: "repackaged.android.test.runner",
+ sdk_version: "current",
static_libs: ["android.test.runner"],
jarjar_rules: "jarjar-rules.txt",
diff --git a/tests/ActivityManagerPerfTests/README.txt b/tests/ActivityManagerPerfTests/README.txt
index 1040ed1..2686290 100644
--- a/tests/ActivityManagerPerfTests/README.txt
+++ b/tests/ActivityManagerPerfTests/README.txt
@@ -5,13 +5,7 @@
* Self-contained perf tests should go in frameworks/base/apct-tests/perftests
Command to run tests
-* atest .../frameworks/base/tests/ActivityManagerPerfTests/tests/
- * Command currently not working: b/71859981
-* m ActivityManagerPerfTests ActivityManagerPerfTestsTestApp && \
- adb install "$OUT"/data/app/ActivityManagerPerfTests/ActivityManagerPerfTests.apk && \
- adb install "$OUT"/data/app/ActivityManagerPerfTestsTestApp/ActivityManagerPerfTestsTestApp.apk && \
- adb shell am instrument -w \
- com.android.frameworks.perftests.amtests/android.support.test.runner.AndroidJUnitRunner
+* atest -v ActivityManagerPerfTests
Overview
* The numbers we are trying to measure are end-to-end numbers
diff --git a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
index f32464b..38c298c 100644
--- a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
@@ -103,6 +103,7 @@
private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh";
private static final String APP_LAUNCH_CMD = "am start -W -n";
private static final String SUCCESS_MESSAGE = "Status: ok";
+ private static final String WARNING_MESSAGE = "Warning: Activity not started";
private static final String COMPILE_SUCCESS = "Success";
private static final String THIS_TIME = "ThisTime:";
private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d";
@@ -231,12 +232,14 @@
dropCache();
String appPkgName = mNameToIntent.get(launch.getApp())
.getComponent().getPackageName();
-
+ Log.v(TAG, String.format("\nApp name: %s", launch.getApp()));
+ Log.v(TAG, String.format("Adding app package name: %s", appPkgName));
// App launch times for trial launch will not be used for final
// launch time calculations.
if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) {
// In the "applaunch.txt" file, trail launches is referenced using
// "TRIAL_LAUNCH"
+ Log.v(TAG, "Trial Launch");
if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) {
assertTrue(String.format("Not able to compile the app : %s", appPkgName),
compileApp(VERIFY_FILTER, appPkgName));
@@ -246,8 +249,14 @@
}
// We only need to run a trial for the speed-profile filter, but we always
// run one for "applaunch.txt" consistency.
- AppLaunchResult launchResult =
- startApp(launch.getApp(), true, launch.getLaunchReason());
+ AppLaunchResult launchResult = null;
+ if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
+ Log.v(TAG, "Home package detected. Not killing app");
+ launchResult = startApp(launch.getApp(), false, launch.getLaunchReason());
+ } else {
+ Log.v(TAG, "Will kill app before launch");
+ launchResult = startApp(launch.getApp(), true, launch.getLaunchReason());
+ }
if (launchResult.mLaunchTime < 0) {
addLaunchResult(launch, new AppLaunchResult());
// simply pass the app if launch isn't successful
@@ -268,6 +277,7 @@
// App launch times used for final calculation
else if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) {
+ Log.v(TAG, "Launch iteration prefix.");
AppLaunchResult launchResults = null;
if (hasFailureOnFirstLaunch(launch)) {
// skip if the app has failures while launched first
@@ -276,8 +286,10 @@
// In the "applaunch.txt" file app launches are referenced using
// "LAUNCH_ITERATION - ITERATION NUM"
if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
+ Log.v(TAG, "Home package detected. Not killing app");
launchResults = startApp(launch.getApp(), false, launch.getLaunchReason());
} else {
+ Log.v(TAG, "Will kill app before launch");
launchResults = startApp(launch.getApp(), true, launch.getLaunchReason());
}
if (launchResults.mLaunchTime < 0) {
@@ -293,6 +305,7 @@
// App launch times for trace launch will not be used for final
// launch time calculations.
else if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) {
+ Log.v(TAG, "Trace iteration prefix");
AtraceLogger atraceLogger = AtraceLogger
.getAtraceLoggerInstance(getInstrumentation());
// Start the trace
@@ -300,7 +313,13 @@
atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize,
traceDumpInterval, rootTraceSubDir,
String.format("%s-%s", launch.getApp(), launch.getLaunchReason()));
- startApp(launch.getApp(), true, launch.getLaunchReason());
+ if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
+ Log.v(TAG, "Home package detected. Not killing app");
+ startApp(launch.getApp(), false, launch.getLaunchReason());
+ } else {
+ Log.v(TAG, "Will kill app before launch");
+ startApp(launch.getApp(), true, launch.getLaunchReason());
+ }
sleep(POST_LAUNCH_IDLE_TIMEOUT);
} finally {
// Stop the trace
@@ -707,7 +726,12 @@
String packageName = mLaunchIntent.getComponent().getPackageName();
String componentName = mLaunchIntent.getComponent().flattenToShortString();
if (mForceStopBeforeLaunch) {
+ Log.v(TAG, "Stopping app before launch");
mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+ } else {
+ Log.v(TAG, "Not killing app. Going to Home Screen.");
+ ParcelFileDescriptor goHome = getInstrumentation().getUiAutomation()
+ .executeShellCommand("input keyevent 3");
}
String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName);
if (mSimplePerfAppOnly) {
@@ -767,6 +791,17 @@
TotalTime: 357
WaitTime: 377
Complete*/
+ /* WHEN NOT KILLING HOME :
+ Starting: Intent { cmp=com.google.android.wearable.app/
+ com.google.android.clockwork.home.calendar.AgendaActivity }
+ Warning: Activity not started, its current task has been brought to the front
+ Status: ok
+ Activity: com.google.android.wearable.app/
+ com.google.android.clockwork.home.calendar.AgendaActivity
+ ThisTime: 209
+ TotalTime: 209
+ WaitTime: 285
+ Complete*/
/* WITH SIMPLEPERF :
Performance counter statistics,
6595722690,cpu-cycles,4.511040,GHz,(100%),
@@ -776,28 +811,32 @@
inputStream));
String line = null;
int lineCount = 1;
+ int addLineForWarning = 0;
mBufferedWriter.newLine();
mBufferedWriter.write(headerInfo);
mBufferedWriter.newLine();
while ((line = bufferedReader.readLine()) != null) {
- if (lineCount == 2 && line.contains(SUCCESS_MESSAGE)) {
+ if (lineCount == 2 && line.contains(WARNING_MESSAGE)) {
+ addLineForWarning = 1;
+ }
+ if (lineCount == (2 + addLineForWarning) && line.contains(SUCCESS_MESSAGE)) {
launchSuccess = true;
}
// Parse TotalTime which is the launch time
- if (launchSuccess && lineCount == 5) {
+ if (launchSuccess && lineCount == (5 + addLineForWarning)) {
String launchSplit[] = line.split(":");
launchTime = launchSplit[1].trim();
}
if (mSimplePerfAppOnly) {
// Parse simpleperf output.
- if (lineCount == 9) {
+ if (lineCount == (9 + addLineForWarning)) {
if (!line.contains("cpu-cycles")) {
Log.e(TAG, "Error in simpleperf output");
} else {
cpuCycles = line.split(",")[0].trim();
}
- } else if (lineCount == 10) {
+ } else if (lineCount == (10 + addLineForWarning)) {
if (!line.contains("major-faults")) {
Log.e(TAG, "Error in simpleperf output");
} else {
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index dc2ed43..948422c 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -62,10 +62,14 @@
shared_libs: [
"libstats_proto_host",
+ "libprotobuf-cpp-full",
],
proto: {
type: "full",
+ include_dirs: [
+ "external/protobuf/src",
+ ],
},
}
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 0e57f7f..ab106d7 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -41,12 +41,12 @@
}
AtomDecl::AtomDecl(const AtomDecl& that)
- :code(that.code),
- name(that.name),
- message(that.message),
- fields(that.fields)
-{
-}
+ : code(that.code),
+ name(that.name),
+ message(that.message),
+ fields(that.fields),
+ primaryFields(that.primaryFields),
+ exclusiveField(that.exclusiveField) {}
AtomDecl::AtomDecl(int c, const string& n, const string& m)
:code(c),
@@ -237,6 +237,31 @@
signature->push_back(javaType);
}
atomDecl->fields.push_back(atField);
+
+ if (field->options().GetExtension(os::statsd::stateFieldOption).option() ==
+ os::statsd::StateField::PRIMARY) {
+ if (javaType == JAVA_TYPE_UNKNOWN ||
+ javaType == JAVA_TYPE_ATTRIBUTION_CHAIN ||
+ javaType == JAVA_TYPE_OBJECT || javaType == JAVA_TYPE_BYTE_ARRAY) {
+ errorCount++;
+ }
+ atomDecl->primaryFields.push_back(it->first);
+ }
+
+ if (field->options().GetExtension(os::statsd::stateFieldOption).option() ==
+ os::statsd::StateField::EXCLUSIVE) {
+ if (javaType == JAVA_TYPE_UNKNOWN ||
+ javaType == JAVA_TYPE_ATTRIBUTION_CHAIN ||
+ javaType == JAVA_TYPE_OBJECT || javaType == JAVA_TYPE_BYTE_ARRAY) {
+ errorCount++;
+ }
+
+ if (atomDecl->exclusiveField == 0) {
+ atomDecl->exclusiveField = it->first;
+ } else {
+ errorCount++;
+ }
+ }
}
return errorCount;
@@ -318,6 +343,9 @@
AtomDecl atomDecl(atomField->number(), atomField->name(), atom->name());
vector<java_type_t> signature;
errorCount += collate_atom(atom, &atomDecl, &signature);
+ if (atomDecl.primaryFields.size() != 0 && atomDecl.exclusiveField == 0) {
+ errorCount++;
+ }
atoms->signatures.insert(signature);
atoms->decls.insert(atomDecl);
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 0455eca..edba3e2 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -81,6 +81,9 @@
string message;
vector<AtomField> fields;
+ vector<int> primaryFields;
+ int exclusiveField = 0;
+
AtomDecl();
AtomDecl(const AtomDecl& that);
AtomDecl(int code, const string& name, const string& message);
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index da67e92..d58c223 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -320,6 +320,7 @@
fprintf(out, "\n");
fprintf(out, "#include <stdint.h>\n");
fprintf(out, "#include <vector>\n");
+ fprintf(out, "#include <map>\n");
fprintf(out, "#include <set>\n");
fprintf(out, "\n");
@@ -412,6 +413,43 @@
fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", maxPushedAtomId);
+ fprintf(out, "struct StateAtomFieldOptions {\n");
+ fprintf(out, " std::vector<int> primaryFields;\n");
+ fprintf(out, " int exclusiveField;\n");
+ fprintf(out, "\n");
+ fprintf(out,
+ " static std::map<int, StateAtomFieldOptions> "
+ "getStateAtomFieldOptions() {\n");
+ fprintf(out, " std::map<int, StateAtomFieldOptions> options;\n");
+ fprintf(out, " StateAtomFieldOptions opt;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding primary and exclusive fields for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+ fprintf(out, " opt.primaryFields.clear();\n");
+ for (const auto& field : atom->primaryFields) {
+ fprintf(out, " opt.primaryFields.push_back(%d);\n", field);
+ }
+
+ fprintf(out, " opt.exclusiveField = %d;\n", atom->exclusiveField);
+ fprintf(out, " options[static_cast<int>(%s)] = opt;\n",
+ make_constant_name(atom->name).c_str());
+ }
+
+ fprintf(out, " return options;\n");
+ fprintf(out, " }\n");
+ fprintf(out, "};\n");
+
+ fprintf(out,
+ "const static std::map<int, StateAtomFieldOptions> "
+ "kStateAtomsFieldOptions = "
+ "StateAtomFieldOptions::getStateAtomFieldOptions();\n");
+
// Print write methods
fprintf(out, "//\n");
fprintf(out, "// Write methods\n");
diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto
index 66cbee8..264a865 100644
--- a/tools/stats_log_api_gen/test.proto
+++ b/tools/stats_log_api_gen/test.proto
@@ -17,6 +17,7 @@
syntax = "proto2";
import "frameworks/base/cmds/statsd/src/atoms.proto";
+import "frameworks/base/cmds/statsd/src/atom_field_options.proto";
package android.stats_log_api_gen;
@@ -108,3 +109,68 @@
oneof event { BadAttributionNodePositionAtom bad = 1; }
}
+message BadStateAtoms {
+ oneof event {
+ BadStateAtom1 bad1 = 1;
+ BadStateAtom2 bad2 = 2;
+ BadStateAtom3 bad3 = 3;
+ }
+}
+
+message GoodStateAtoms {
+ oneof event {
+ GoodStateAtom1 good1 = 1;
+ GoodStateAtom2 good2 = 2;
+ }
+}
+
+// The atom has only primary field but no exclusive state field.
+message BadStateAtom1 {
+ optional int32 uid = 1
+ [(android.os.statsd.stateFieldOption).option = PRIMARY];
+}
+
+// Only primative types can be annotated.
+message BadStateAtom2 {
+ repeated android.os.statsd.AttributionNode attribution = 1
+ [(android.os.statsd.stateFieldOption).option = PRIMARY];
+ optional int32 state = 2
+ [(android.os.statsd.stateFieldOption).option = EXCLUSIVE];
+}
+
+// Having 2 exclusive state field in the atom means the atom is badly designed.
+// E.g., putting bluetooth state and wifi state in the same atom.
+message BadStateAtom3 {
+ optional int32 uid = 1
+ [(android.os.statsd.stateFieldOption).option = PRIMARY];
+ optional int32 state = 2
+ [(android.os.statsd.stateFieldOption).option = EXCLUSIVE];
+ optional int32 state2 = 3
+ [(android.os.statsd.stateFieldOption).option = EXCLUSIVE];
+}
+
+message GoodStateAtom1 {
+ optional int32 uid = 1
+ [(android.os.statsd.stateFieldOption).option = PRIMARY];
+ optional int32 state = 2
+ [(android.os.statsd.stateFieldOption).option = EXCLUSIVE];
+}
+
+// Atoms can have exclusive state field, but no primary field. That means
+// the state is globally exclusive (e.g., DisplayState).
+message GoodStateAtom2 {
+ optional int32 uid = 1;
+ optional int32 state = 2
+ [(android.os.statsd.stateFieldOption).option = EXCLUSIVE];
+}
+
+// We can have more than one primary fields. That means their combination is a
+// primary key.
+message GoodStateAtom3 {
+ optional int32 uid = 1
+ [(android.os.statsd.stateFieldOption).option = PRIMARY];
+ optional int32 tid = 2
+ [(android.os.statsd.stateFieldOption).option = PRIMARY];
+ optional int32 state = 3
+ [(android.os.statsd.stateFieldOption).option = EXCLUSIVE];
+}
\ No newline at end of file
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
index 9e22cd9..1936d96 100644
--- a/tools/stats_log_api_gen/test_collation.cpp
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -199,5 +199,18 @@
EXPECT_EQ(1, errorCount);
}
+TEST(CollationTest, FailOnBadStateAtomOptions) {
+ Atoms atoms;
+ int errorCount = collate_atoms(BadStateAtoms::descriptor(), &atoms);
+
+ EXPECT_EQ(3, errorCount);
+}
+
+TEST(CollationTest, PassOnGoodStateAtomOptions) {
+ Atoms atoms;
+ int errorCount = collate_atoms(GoodStateAtoms::descriptor(), &atoms);
+ EXPECT_EQ(0, errorCount);
+}
+
} // namespace stats_log_api_gen
} // namespace android
\ No newline at end of file