Merge "Import translations. DO NOT MERGE"
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 2a01cb1..bc2d893 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";
@@ -5349,18 +5349,18 @@
ctor public Notification.Action.WearableExtender(android.app.Notification.Action);
method public android.app.Notification.Action.WearableExtender clone();
method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Builder);
- method public java.lang.CharSequence getCancelLabel();
- method public java.lang.CharSequence getConfirmLabel();
+ method public deprecated java.lang.CharSequence getCancelLabel();
+ method public deprecated java.lang.CharSequence getConfirmLabel();
method public boolean getHintDisplayActionInline();
method public boolean getHintLaunchesActivity();
- method public java.lang.CharSequence getInProgressLabel();
+ method public deprecated java.lang.CharSequence getInProgressLabel();
method public boolean isAvailableOffline();
method public android.app.Notification.Action.WearableExtender setAvailableOffline(boolean);
- method public android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
- method public android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+ method public deprecated android.app.Notification.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
+ method public deprecated android.app.Notification.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
method public android.app.Notification.Action.WearableExtender setHintDisplayActionInline(boolean);
method public android.app.Notification.Action.WearableExtender setHintLaunchesActivity(boolean);
- method public android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
+ method public deprecated android.app.Notification.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
}
public static class Notification.BigPictureStyle extends android.app.Notification.Style {
@@ -5583,39 +5583,39 @@
method public android.graphics.Bitmap getBackground();
method public java.lang.String getBridgeTag();
method public int getContentAction();
- method public int getContentIcon();
- method public int getContentIconGravity();
+ method public deprecated int getContentIcon();
+ method public deprecated int getContentIconGravity();
method public boolean getContentIntentAvailableOffline();
- method public int getCustomContentHeight();
- method public int getCustomSizePreset();
+ method public deprecated int getCustomContentHeight();
+ method public deprecated int getCustomSizePreset();
method public java.lang.String getDismissalId();
method public android.app.PendingIntent getDisplayIntent();
- method public int getGravity();
+ method public deprecated int getGravity();
method public boolean getHintAmbientBigPicture();
- method public boolean getHintAvoidBackgroundClipping();
+ method public deprecated boolean getHintAvoidBackgroundClipping();
method public boolean getHintContentIntentLaunchesActivity();
- method public boolean getHintHideIcon();
- method public int getHintScreenTimeout();
- method public boolean getHintShowBackgroundOnly();
+ method public deprecated boolean getHintHideIcon();
+ method public deprecated int getHintScreenTimeout();
+ method public deprecated boolean getHintShowBackgroundOnly();
method public java.util.List<android.app.Notification> getPages();
method public boolean getStartScrollBottom();
method public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap);
method public android.app.Notification.WearableExtender setBridgeTag(java.lang.String);
method public android.app.Notification.WearableExtender setContentAction(int);
- method public android.app.Notification.WearableExtender setContentIcon(int);
- method public android.app.Notification.WearableExtender setContentIconGravity(int);
+ method public deprecated android.app.Notification.WearableExtender setContentIcon(int);
+ method public deprecated android.app.Notification.WearableExtender setContentIconGravity(int);
method public android.app.Notification.WearableExtender setContentIntentAvailableOffline(boolean);
- method public android.app.Notification.WearableExtender setCustomContentHeight(int);
- method public android.app.Notification.WearableExtender setCustomSizePreset(int);
+ method public deprecated android.app.Notification.WearableExtender setCustomContentHeight(int);
+ method public deprecated android.app.Notification.WearableExtender setCustomSizePreset(int);
method public android.app.Notification.WearableExtender setDismissalId(java.lang.String);
method public android.app.Notification.WearableExtender setDisplayIntent(android.app.PendingIntent);
- method public android.app.Notification.WearableExtender setGravity(int);
+ method public deprecated android.app.Notification.WearableExtender setGravity(int);
method public android.app.Notification.WearableExtender setHintAmbientBigPicture(boolean);
- method public android.app.Notification.WearableExtender setHintAvoidBackgroundClipping(boolean);
+ method public deprecated android.app.Notification.WearableExtender setHintAvoidBackgroundClipping(boolean);
method public android.app.Notification.WearableExtender setHintContentIntentLaunchesActivity(boolean);
- method public android.app.Notification.WearableExtender setHintHideIcon(boolean);
- method public android.app.Notification.WearableExtender setHintScreenTimeout(int);
- method public android.app.Notification.WearableExtender setHintShowBackgroundOnly(boolean);
+ method public deprecated android.app.Notification.WearableExtender setHintHideIcon(boolean);
+ method public deprecated android.app.Notification.WearableExtender setHintScreenTimeout(int);
+ method public deprecated android.app.Notification.WearableExtender setHintShowBackgroundOnly(boolean);
method public android.app.Notification.WearableExtender setStartScrollBottom(boolean);
field public static final int SCREEN_TIMEOUT_LONG = -1; // 0xffffffff
field public static final int SCREEN_TIMEOUT_SHORT = 0; // 0x0
@@ -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";
@@ -21905,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
@@ -41820,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);
@@ -41853,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 {
@@ -42729,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);
@@ -42838,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 e0c447a..1235591 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -125,6 +125,7 @@
field public static final java.lang.String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
field public static final java.lang.String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
field public static final java.lang.String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
+ field public static final java.lang.String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
field public static final java.lang.String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
field public static final java.lang.String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
field public static final java.lang.String READ_FRAME_BUFFER = "android.permission.READ_FRAME_BUFFER";
@@ -469,7 +470,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);
@@ -4298,10 +4303,9 @@
method public void recoverySecretAvailable(android.security.keystore.recovery.KeyChainProtectionParams) throws android.security.keystore.recovery.InternalRecoveryServiceException;
method public void removeKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
method public void setRecoverySecretTypes(int[]) throws android.security.keystore.recovery.InternalRecoveryServiceException;
- method public void setRecoveryStatus(java.lang.String, java.lang.String, int) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.content.pm.PackageManager.NameNotFoundException;
+ method public void setRecoveryStatus(java.lang.String, int) throws android.security.keystore.recovery.InternalRecoveryServiceException;
method public void setServerParams(byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException;
method public void setSnapshotCreatedPendingIntent(android.app.PendingIntent) throws android.security.keystore.recovery.InternalRecoveryServiceException;
- field public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2; // 0x2
field public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3; // 0x3
field public static final int RECOVERY_STATUS_SYNCED = 0; // 0x0
field public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1; // 0x1
@@ -6160,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/system-removed.txt b/api/system-removed.txt
index 48f43e0..ac40052 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -91,6 +91,14 @@
}
+package android.security.keystore.recovery {
+
+ public class RecoveryController {
+ method public deprecated void setRecoveryStatus(java.lang.String, java.lang.String, int) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.content.pm.PackageManager.NameNotFoundException;
+ }
+
+}
+
package android.service.notification {
public abstract class NotificationListenerService extends android.app.Service {
diff --git a/api/test-current.txt b/api/test-current.txt
index 049211d..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 {
@@ -1097,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/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 f9cb381..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;
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/Activity.java b/core/java/android/app/Activity.java
index 83fe4dd..33deca7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5553,13 +5553,7 @@
if (mParent != null) {
throw new IllegalStateException("Can only be called on top-level activity");
}
- if (Looper.myLooper() != mMainThread.getLooper()) {
- throw new IllegalStateException("Must be called from main thread");
- }
- try {
- ActivityManager.getService().requestActivityRelaunch(mToken);
- } catch (RemoteException e) {
- }
+ mMainThread.handleRelaunchActivityLocally(mToken);
}
/**
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/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a69b0ee..302ffed 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4292,19 +4292,15 @@
View.mDebugViewAttributes = debugViewAttributes;
// request all activities to relaunch for the changes to take place
- requestRelaunchAllActivities();
+ relaunchAllActivities();
}
}
- private void requestRelaunchAllActivities() {
+ private void relaunchAllActivities() {
for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
final Activity activity = entry.getValue().activity;
if (!activity.mFinished) {
- try {
- ActivityManager.getService().requestActivityRelaunch(entry.getKey());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ handleRelaunchActivityLocally(entry.getKey());
}
}
}
@@ -4679,42 +4675,81 @@
throw e.rethrowFromSystemServer();
}
- // Need to ensure state is saved.
- if (!r.paused) {
- performPauseActivity(r, false, "handleRelaunchActivity",
- null /* pendingActions */);
+ handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
+ pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
+
+ if (pendingActions != null) {
+ // Only report a successful relaunch to WindowManager.
+ pendingActions.setReportRelaunchToWindowManager(true);
}
- if (!r.stopped) {
- callActivityOnStop(r, true /* saveState */, "handleRelaunchActivity");
+ }
+
+ /** Performs the activity relaunch locally vs. requesting from system-server. */
+ void handleRelaunchActivityLocally(IBinder token) {
+ if (Looper.myLooper() != getLooper()) {
+ throw new IllegalStateException("Must be called from main thread");
}
- handleDestroyActivity(r.token, false, configChanges, true, "handleRelaunchActivity");
+ final ActivityClientRecord r = mActivities.get(token);
+ if (r == null) {
+ return;
+ }
+
+ final int prevState = r.getLifecycleState();
+
+ if (prevState < ON_RESUME) {
+ Log.w(TAG, "Activity needs to be already resumed in other to be relaunched.");
+ return;
+ }
+
+ // TODO(b/73747058): Investigate converting this to use transaction to relaunch.
+ handleRelaunchActivityInner(r, 0 /* configChanges */, null /* pendingResults */,
+ null /* pendingIntents */, null /* pendingActions */, prevState != ON_RESUME,
+ r.overrideConfig, "handleRelaunchActivityLocally");
+
+ // Restore back to the previous state before relaunch if needed.
+ if (prevState != r.getLifecycleState()) {
+ mTransactionExecutor.cycleToPath(r, prevState);
+ }
+ }
+
+ private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
+ PendingTransactionActions pendingActions, boolean startsNotResumed,
+ Configuration overrideConfig, String reason) {
+ // Need to ensure state is saved.
+ if (!r.paused) {
+ performPauseActivity(r, false, reason, null /* pendingActions */);
+ }
+ if (!r.stopped) {
+ callActivityOnStop(r, true /* saveState */, reason);
+ }
+
+ handleDestroyActivity(r.token, false, configChanges, true, reason);
r.activity = null;
r.window = null;
r.hideForNow = false;
r.nextIdle = null;
// Merge any pending results and pending intents; don't just replace them
- if (tmp.pendingResults != null) {
+ if (pendingResults != null) {
if (r.pendingResults == null) {
- r.pendingResults = tmp.pendingResults;
+ r.pendingResults = pendingResults;
} else {
- r.pendingResults.addAll(tmp.pendingResults);
+ r.pendingResults.addAll(pendingResults);
}
}
- if (tmp.pendingIntents != null) {
+ if (pendingIntents != null) {
if (r.pendingIntents == null) {
- r.pendingIntents = tmp.pendingIntents;
+ r.pendingIntents = pendingIntents;
} else {
- r.pendingIntents.addAll(tmp.pendingIntents);
+ r.pendingIntents.addAll(pendingIntents);
}
}
- r.startsNotResumed = tmp.startsNotResumed;
- r.overrideConfig = tmp.overrideConfig;
+ r.startsNotResumed = startsNotResumed;
+ r.overrideConfig = overrideConfig;
handleLaunchActivity(r, pendingActions);
- // Only report a successful relaunch to WindowManager.
- pendingActions.setReportRelaunchToWindowManager(true);
}
@Override
@@ -5117,7 +5152,7 @@
newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1;
handleConfigurationChanged(newConfig, null);
- requestRelaunchAllActivities();
+ relaunchAllActivities();
}
static void freeTextLayoutCachesIfNeeded(int configDiff) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 54fd0c4..ac301b3 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -630,7 +630,6 @@
void setHasTopUi(boolean hasTopUi);
// Start of O transactions
- void requestActivityRelaunch(in IBinder token);
/**
* Updates override configuration applied to specific display.
* @param values Update values for display configuration. If null is passed it will request the
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8361711..233e09d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1825,6 +1825,7 @@
* @param label the label to display while the action is being prepared to execute
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setInProgressLabel(CharSequence label) {
mInProgressLabel = label;
return this;
@@ -1836,6 +1837,7 @@
*
* @return the label to display while the action is being prepared to execute
*/
+ @Deprecated
public CharSequence getInProgressLabel() {
return mInProgressLabel;
}
@@ -1847,6 +1849,7 @@
* @param label the label to confirm the action should be executed
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setConfirmLabel(CharSequence label) {
mConfirmLabel = label;
return this;
@@ -1858,6 +1861,7 @@
*
* @return the label to confirm the action should be executed
*/
+ @Deprecated
public CharSequence getConfirmLabel() {
return mConfirmLabel;
}
@@ -1869,6 +1873,7 @@
* @param label the label to display to cancel the action
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setCancelLabel(CharSequence label) {
mCancelLabel = label;
return this;
@@ -1880,6 +1885,7 @@
*
* @return the label to display to cancel the action
*/
+ @Deprecated
public CharSequence getCancelLabel() {
return mCancelLabel;
}
@@ -8082,6 +8088,7 @@
/**
* Set an icon that goes with the content of this notification.
*/
+ @Deprecated
public WearableExtender setContentIcon(int icon) {
mContentIcon = icon;
return this;
@@ -8090,6 +8097,7 @@
/**
* Get an icon that goes with the content of this notification.
*/
+ @Deprecated
public int getContentIcon() {
return mContentIcon;
}
@@ -8100,6 +8108,7 @@
* {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
* @see #setContentIcon
*/
+ @Deprecated
public WearableExtender setContentIconGravity(int contentIconGravity) {
mContentIconGravity = contentIconGravity;
return this;
@@ -8111,6 +8120,7 @@
* {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
* @see #getContentIcon
*/
+ @Deprecated
public int getContentIconGravity() {
return mContentIconGravity;
}
@@ -8158,6 +8168,7 @@
* {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
* The default value is {@link android.view.Gravity#BOTTOM}.
*/
+ @Deprecated
public WearableExtender setGravity(int gravity) {
mGravity = gravity;
return this;
@@ -8169,6 +8180,7 @@
* {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
* The default value is {@link android.view.Gravity#BOTTOM}.
*/
+ @Deprecated
public int getGravity() {
return mGravity;
}
@@ -8182,6 +8194,7 @@
* documentation for the preset in question. See also
* {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
*/
+ @Deprecated
public WearableExtender setCustomSizePreset(int sizePreset) {
mCustomSizePreset = sizePreset;
return this;
@@ -8195,6 +8208,7 @@
* using {@link #setDisplayIntent}. Check the documentation for the preset in question.
* See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
*/
+ @Deprecated
public int getCustomSizePreset() {
return mCustomSizePreset;
}
@@ -8206,6 +8220,7 @@
* {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
* {@link #getCustomContentHeight}.
*/
+ @Deprecated
public WearableExtender setCustomContentHeight(int height) {
mCustomContentHeight = height;
return this;
@@ -8217,6 +8232,7 @@
* using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
* {@link #setCustomContentHeight}.
*/
+ @Deprecated
public int getCustomContentHeight() {
return mCustomContentHeight;
}
@@ -8267,6 +8283,7 @@
* @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setHintHideIcon(boolean hintHideIcon) {
setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
return this;
@@ -8277,6 +8294,7 @@
* @return {@code true} if this icon should not be displayed, false otherwise.
* The default value is {@code false} if this was never set.
*/
+ @Deprecated
public boolean getHintHideIcon() {
return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
}
@@ -8286,6 +8304,7 @@
* displayed, and other semantic content should be hidden. This hint is only applicable
* to sub-pages added using {@link #addPage}.
*/
+ @Deprecated
public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
return this;
@@ -8296,6 +8315,7 @@
* displayed, and other semantic content should be hidden. This hint is only applicable
* to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
*/
+ @Deprecated
public boolean getHintShowBackgroundOnly() {
return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
}
@@ -8307,6 +8327,7 @@
* @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setHintAvoidBackgroundClipping(
boolean hintAvoidBackgroundClipping) {
setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
@@ -8320,6 +8341,7 @@
* @return {@code true} if it's ok if the background is clipped on the screen, false
* otherwise. The default value is {@code false} if this was never set.
*/
+ @Deprecated
public boolean getHintAvoidBackgroundClipping() {
return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
}
@@ -8331,6 +8353,7 @@
* {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setHintScreenTimeout(int timeout) {
mHintScreenTimeout = timeout;
return this;
@@ -8342,6 +8365,7 @@
* @return the duration in milliseconds if > 0, or either one of the sentinel values
* {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
*/
+ @Deprecated
public int getHintScreenTimeout() {
return mHintScreenTimeout;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 542c1d2..9d4d943 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1646,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
@@ -1682,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
})
@@ -2854,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.
@@ -7193,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
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/app/timezone/RulesManager.java b/core/java/android/app/timezone/RulesManager.java
index dc79256..fe83113 100644
--- a/core/java/android/app/timezone/RulesManager.java
+++ b/core/java/android/app/timezone/RulesManager.java
@@ -36,7 +36,7 @@
* <p>This interface is intended for use with the default APK-based time zone rules update
* application but it can also be used by OEMs if that mechanism is turned off using configuration.
* All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
- * permission.
+ * permission unless otherwise stated.
*
* <p>When using the default mechanism, when properly configured the Android system will send a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
@@ -120,9 +120,12 @@
/**
* Returns information about the current time zone rules state such as the IANA version of
- * the system and any currently installed distro. This method is intended to allow clients to
- * determine if the current state can be improved; for example by passing the information to a
- * server that may provide a new distro for download.
+ * the system and any currently installed distro. This method allows clients to determine the
+ * current device state, perhaps to see if it can be improved; for example by passing the
+ * information to a server that may provide a new distro for download.
+ *
+ * <p>Callers must possess the {@link android.Manifest.permission#QUERY_TIME_ZONE_RULES} system
+ * permission.
*/
public RulesState getRulesState() {
try {
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/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/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/security/keystore/RecoveryController.java b/core/java/android/security/keystore/RecoveryController.java
index 98e6a20..786d454 100644
--- a/core/java/android/security/keystore/RecoveryController.java
+++ b/core/java/android/security/keystore/RecoveryController.java
@@ -47,6 +47,7 @@
* <p>A recovery agent requires the privileged permission
* {@code android.Manifest.permission#RECOVER_KEYSTORE}.
*
+ * @deprecated Use {@link android.security.keystore.recovery.RecoveryController}.
* @hide
*/
public class RecoveryController {
@@ -259,7 +260,9 @@
@NonNull String packageName, @Nullable String[] aliases, int status)
throws NameNotFoundException, InternalRecoveryServiceException {
try {
- mBinder.setRecoveryStatus(packageName, aliases, status);
+ for (String alias : aliases) {
+ mBinder.setRecoveryStatus(alias, status);
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java
index 7cd08f7..d57782c 100644
--- a/core/java/android/security/keystore/recovery/RecoveryController.java
+++ b/core/java/android/security/keystore/recovery/RecoveryController.java
@@ -65,8 +65,6 @@
public static final int RECOVERY_STATUS_SYNCED = 0;
/** Waiting for recovery agent to sync the key. */
public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
- /** Recovery account is not available. */
- public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
/** Key cannot be synced. */
public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
@@ -290,24 +288,33 @@
}
/**
- * Updates recovery status for given key. It is used to notify keystore that key was
- * successfully stored on the server or there were an error. Application can check this value
- * using {@code getRecoveyStatus}.
- *
- * @param packageName Application whose recoverable key's status are to be updated.
- * @param alias Application-specific key alias.
- * @param status Status specific to recovery agent.
- * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
- * service.
+ * @deprecated Use {@link #setRecoveryStatus(String, int)}
+ * @removed
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
public void setRecoveryStatus(
@NonNull String packageName, String alias, int status)
throws NameNotFoundException, InternalRecoveryServiceException {
+ setRecoveryStatus(alias, status);
+ }
+
+ /**
+ * Sets the recovery status for given key. It is used to notify the keystore that the key was
+ * successfully stored on the server or that there was an error. An application can check this
+ * value using {@link #getRecoveryStatus(String, String)}.
+ *
+ * @param alias The alias of the key whose status to set.
+ * @param status The status of the key. One of {@link #RECOVERY_STATUS_SYNCED},
+ * {@link #RECOVERY_STATUS_SYNC_IN_PROGRESS} or {@link #RECOVERY_STATUS_PERMANENT_FAILURE}.
+ * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
+ * service.
+ */
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
+ public void setRecoveryStatus(String alias, int status)
+ throws InternalRecoveryServiceException {
try {
- // TODO: update aidl
- String[] aliases = alias == null ? null : new String[]{alias};
- mBinder.setRecoveryStatus(packageName, aliases, status);
+ mBinder.setRecoveryStatus(alias, status);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
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/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/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 2723030..f5b7068 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -23,6 +23,8 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Path;
@@ -38,6 +40,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
@@ -73,6 +76,17 @@
public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION,
new Size(0, 0));
+
+ private static final Object CACHE_LOCK = new Object();
+ @GuardedBy("CACHE_LOCK")
+ private static String sCachedSpec;
+ @GuardedBy("CACHE_LOCK")
+ private static int sCachedDisplayWidth;
+ @GuardedBy("CACHE_LOCK")
+ private static float sCachedDensity;
+ @GuardedBy("CACHE_LOCK")
+ private static DisplayCutout sCachedCutout;
+
private final Rect mSafeInsets;
private final Region mBounds;
private final Size mFrameSize;
@@ -350,10 +364,26 @@
* @hide
*/
public static DisplayCutout fromResources(Resources res, int displayWidth) {
- String spec = res.getString(R.string.config_mainBuiltInDisplayCutout);
+ return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout),
+ displayWidth, res.getDisplayMetrics().density);
+ }
+
+ /**
+ * Creates an instance according to the supplied {@link android.util.PathParser.PathData} spec.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public static DisplayCutout fromSpec(String spec, int displayWidth, float density) {
if (TextUtils.isEmpty(spec)) {
return null;
}
+ synchronized (CACHE_LOCK) {
+ if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth
+ && sCachedDensity == density) {
+ return sCachedCutout;
+ }
+ }
spec = spec.trim();
final boolean inDp = spec.endsWith(DP_MARKER);
if (inDp) {
@@ -370,12 +400,19 @@
final Matrix m = new Matrix();
if (inDp) {
- final float dpToPx = res.getDisplayMetrics().density;
- m.postScale(dpToPx, dpToPx);
+ m.postScale(density, density);
}
m.postTranslate(displayWidth / 2f, 0);
p.transform(m);
- return fromBounds(p);
+
+ final DisplayCutout result = fromBounds(p);
+ synchronized (CACHE_LOCK) {
+ sCachedSpec = spec;
+ sCachedDisplayWidth = displayWidth;
+ sCachedDensity = density;
+ sCachedCutout = result;
+ }
+ return result;
}
/**
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/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/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/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 732534c..17e498c 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -72,7 +72,7 @@
void setSnapshotCreatedPendingIntent(in PendingIntent intent);
Map getRecoverySnapshotVersions();
void setServerParams(in byte[] serverParams);
- void setRecoveryStatus(in String packageName, in String[] aliases, int status);
+ void setRecoveryStatus(in String alias, int status);
Map getRecoveryStatus(in String packageName);
void setRecoverySecretTypes(in int[] secretTypes);
int[] getRecoverySecretTypes();
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/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 2f78345..a7178a0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2402,6 +2402,12 @@
<permission android:name="android.permission.UPDATE_CONFIG"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application to query the current time zone rules state
+ on device.
+ @SystemApi @hide -->
+ <permission android:name="android.permission.QUERY_TIME_ZONE_RULES"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows a time zone rule updater application to request
the system installs / uninstalls timezone rules.
<p>An application requesting this permission is responsible for
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/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/config.xml b/core/res/res/values/config.xml
index 0e4d47c..3b963d1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2518,6 +2518,32 @@
in the display pipeline plus some slack just to be sure. -->
<integer name="config_drawLockTimeoutMillis">120</integer>
+ <!-- An array of device capabilities defined by GSMA SGP.22 v2.0.
+ The first item is the capability name that the device supports. The second item is the
+ major version. The minor and revision versions are default to 0s.
+ The device capabilities and their definition in the spec are:
+ gsm : gsmSupportedRelease
+ utran : utranSupportedRelease
+ cdma1x : cdma2000onexSupportedRelease
+ hrpd : cdma2000hrpdSupportedRelease
+ ehrpd : cdma2000ehrpdSupportedRelease
+ eutran : eutranSupportedRelease
+ nfc : contactlessSupportedRelease
+ crl : rspCrlSupportedVersion
+ -->
+ <string-array translatable="false" name="config_telephonyEuiccDeviceCapabilities">
+ <!-- Example:
+ <item>"gsm,11"</item>
+ <item>"utran,11"</item>
+ <item>"cdma1x,1"</item>
+ <item>"hrpd,3"</item>
+ <item>"ehrpd,12"</item>
+ <item>"eutran,11"</item>
+ <item>"nfc,1"</item>
+ <item>"crl,1"</item>
+ -->
+ </string-array>
+
<!-- default telephony hardware configuration for this platform.
-->
<!-- this string array should be overridden by the device to present a list
@@ -3163,7 +3189,7 @@
<!-- 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.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0493c2b..ab4c12b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1222,6 +1222,7 @@
<java-symbol type="array" name="config_disabledUntilUsedPreinstalledCarrierApps" />
<java-symbol type="array" name="config_callBarringMMI" />
<java-symbol type="array" name="config_globalActionsList" />
+ <java-symbol type="array" name="config_telephonyEuiccDeviceCapabilities" />
<java-symbol type="array" name="config_telephonyHardware" />
<java-symbol type="array" name="config_keySystemUuidMapping" />
<java-symbol type="array" name="config_gpsParameters" />
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index ee4bc34..d807353 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -18,10 +18,14 @@
import static android.view.DisplayCutout.NO_CUTOUT;
import static android.view.DisplayCutout.fromBoundingRect;
+import static android.view.DisplayCutout.fromSpec;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import android.graphics.Rect;
@@ -271,6 +275,30 @@
}
@Test
+ public void fromSpec_caches() {
+ DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 1f);
+ assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), sameInstance(cached));
+ }
+
+ @Test
+ public void fromSpec_wontCacheIfSpecChanges() {
+ DisplayCutout cached = fromSpec("L1,0 L1000,1000 L0,1 z", 200, 1f);
+ assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), not(sameInstance(cached)));
+ }
+
+ @Test
+ public void fromSpec_wontCacheIfScreenWidthChanges() {
+ DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 2000, 1f);
+ assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), not(sameInstance(cached)));
+ }
+
+ @Test
+ public void fromSpec_wontCacheIfDensityChanges() {
+ DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 2f);
+ assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), not(sameInstance(cached)));
+ }
+
+ @Test
public void parcel_unparcel_nocutout() {
Parcel p = Parcel.obtain();
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/hiddenapi-package-whitelist.xml b/data/etc/hiddenapi-package-whitelist.xml
index 54d8a23..1d46d42 100644
--- a/data/etc/hiddenapi-package-whitelist.xml
+++ b/data/etc/hiddenapi-package-whitelist.xml
@@ -21,6 +21,81 @@
-->
<config>
+ <hidden-api-whitelisted-app package="android.car.cluster.loggingrenderer" />
+ <hidden-api-whitelisted-app package="android.car.input.service" />
+ <hidden-api-whitelisted-app package="android.car.usb.handler" />
+ <hidden-api-whitelisted-app package="android.ext.services" />
+ <hidden-api-whitelisted-app package="android.ext.shared" />
+ <hidden-api-whitelisted-app package="com.android.backupconfirm" />
+ <hidden-api-whitelisted-app package="com.android.bluetooth" />
+ <hidden-api-whitelisted-app package="com.android.bluetoothdebug" />
+ <hidden-api-whitelisted-app package="com.android.bluetoothmidiservice" />
+ <hidden-api-whitelisted-app package="com.android.calllogbackup" />
+ <hidden-api-whitelisted-app package="com.android.captiveportallogin" />
+ <hidden-api-whitelisted-app package="com.android.car" />
+ <hidden-api-whitelisted-app package="com.android.car.hvac" />
+ <hidden-api-whitelisted-app package="com.android.car.mapsplaceholder" />
+ <hidden-api-whitelisted-app package="com.android.car.media" />
+ <hidden-api-whitelisted-app package="com.android.car.media.localmediaplayer" />
+ <hidden-api-whitelisted-app package="com.android.car.radio" />
+ <hidden-api-whitelisted-app package="com.android.car.settings" />
+ <hidden-api-whitelisted-app package="com.android.car.systemupdater" />
+ <hidden-api-whitelisted-app package="com.android.car.trust" />
+ <hidden-api-whitelisted-app package="com.android.carrierconfig" />
+ <hidden-api-whitelisted-app package="com.android.carrierdefaultapp" />
+ <hidden-api-whitelisted-app package="com.android.cellbroadcastreceiver" />
+ <hidden-api-whitelisted-app package="com.android.certinstaller" />
+ <hidden-api-whitelisted-app package="com.android.customlocale2" />
+ <hidden-api-whitelisted-app package="com.android.defcontainer" />
+ <hidden-api-whitelisted-app package="com.android.documentsui" />
+ <hidden-api-whitelisted-app package="com.android.egg" />
+ <hidden-api-whitelisted-app package="com.android.email.policy" />
+ <hidden-api-whitelisted-app package="com.android.emergency" />
+ <hidden-api-whitelisted-app package="com.android.externalstorage" />
+ <hidden-api-whitelisted-app package="com.android.fakeoemfeatures" />
+ <hidden-api-whitelisted-app package="com.android.gallery" />
+ <hidden-api-whitelisted-app package="com.android.hotspot2" />
+ <hidden-api-whitelisted-app package="com.android.inputdevices" />
+ <hidden-api-whitelisted-app package="com.android.keychain" />
+ <hidden-api-whitelisted-app package="com.android.location.fused" />
+ <hidden-api-whitelisted-app package="com.android.managedprovisioning" />
+ <hidden-api-whitelisted-app package="com.android.mms.service" />
+ <hidden-api-whitelisted-app package="com.android.mtp" />
+ <hidden-api-whitelisted-app package="com.android.nfc" />
+ <hidden-api-whitelisted-app package="com.android.osu" />
+ <hidden-api-whitelisted-app package="com.android.packageinstaller" />
+ <hidden-api-whitelisted-app package="com.android.pacprocessor" />
+ <hidden-api-whitelisted-app package="com.android.phone" />
+ <hidden-api-whitelisted-app package="com.android.pmc" />
+ <hidden-api-whitelisted-app package="com.android.providers.blockednumber" />
<hidden-api-whitelisted-app package="com.android.providers.contacts" />
+ <hidden-api-whitelisted-app package="com.android.providers.downloads" />
+ <hidden-api-whitelisted-app package="com.android.providers.downloads.ui" />
+ <hidden-api-whitelisted-app package="com.android.providers.media" />
+ <hidden-api-whitelisted-app package="com.android.providers.settings" />
+ <hidden-api-whitelisted-app package="com.android.providers.telephony" />
+ <hidden-api-whitelisted-app package="com.android.providers.userdictionary" />
+ <hidden-api-whitelisted-app package="com.android.provision" />
+ <hidden-api-whitelisted-app package="com.android.proxyhandler" />
+ <hidden-api-whitelisted-app package="com.android.sdksetup" />
+ <hidden-api-whitelisted-app package="com.android.se" />
+ <hidden-api-whitelisted-app package="com.android.server.telecom" />
+ <hidden-api-whitelisted-app package="com.android.service.ims" />
+ <hidden-api-whitelisted-app package="com.android.service.ims.presence" />
+ <hidden-api-whitelisted-app package="com.android.settings" />
+ <hidden-api-whitelisted-app package="com.android.sharedstoragebackup" />
+ <hidden-api-whitelisted-app package="com.android.shell" />
+ <hidden-api-whitelisted-app package="com.android.stk" />
+ <hidden-api-whitelisted-app package="com.android.support.car.lenspicker" />
+ <hidden-api-whitelisted-app package="com.android.systemui" />
+ <hidden-api-whitelisted-app package="com.android.systemui.theme.dark" />
+ <hidden-api-whitelisted-app package="com.android.timezone.updater" />
+ <hidden-api-whitelisted-app package="com.android.traceur" />
+ <hidden-api-whitelisted-app package="com.android.tv.settings" />
+ <hidden-api-whitelisted-app package="com.android.vpndialogs" />
+ <hidden-api-whitelisted-app package="com.android.wallpaper.livepicker" />
+ <hidden-api-whitelisted-app package="com.android.wallpaperbackup" />
+ <hidden-api-whitelisted-app package="com.android.wallpapercropper" />
+ <hidden-api-whitelisted-app package="com.googlecode.android_scripting" />
</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/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index 02c71b2..205ce8d 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -328,6 +328,7 @@
RandomAccessFile file = null;
try {
file = new RandomAccessFile(path, "rws");
+ file.setLength(0);
FileDescriptor fd = file.getFD();
setUpMediaMuxer(fd, format);
} finally {
diff --git a/packages/MtpDocumentsProvider/res/values-bs/strings.xml b/packages/MtpDocumentsProvider/res/values-bs/strings.xml
index 33323f8..18c2363 100644
--- a/packages/MtpDocumentsProvider/res/values-bs/strings.xml
+++ b/packages/MtpDocumentsProvider/res/values-bs/strings.xml
@@ -19,7 +19,7 @@
<string name="app_label" msgid="6271216747302322594">"MTP Host"</string>
<string name="downloads_app_label" msgid="7120690641874849726">"Preuzimanja"</string>
<string name="root_name" msgid="5819495383921089536">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> <xliff:g id="STORAGE_NAME">%2$s</xliff:g>"</string>
- <string name="accessing_notification_title" msgid="3030133609230917944">"Pristupanje datotekama iz uređaja <xliff:g id="DEVICE_MODEL">%1$s</xliff:g>"</string>
+ <string name="accessing_notification_title" msgid="3030133609230917944">"Pristupanje fajlovima iz uređaja <xliff:g id="DEVICE_MODEL">%1$s</xliff:g>"</string>
<string name="error_busy_device" msgid="3997316850357386589">"Drugi uređaj je zauzet. Nećete moći prenositi fajlove dok ne bude dostupan."</string>
<string name="error_locked_device" msgid="7557872102188356147">"Fajlovi nisu pronađeni. Moguće je da je drugi uređaj zaključan. Ako jeste, otključajte ga i pokušajte ponovo."</string>
</resources>
diff --git a/packages/PrintSpooler/res/values-as/strings.xml b/packages/PrintSpooler/res/values-as/strings.xml
new file mode 100644
index 0000000..2beaac8
--- /dev/null
+++ b/packages/PrintSpooler/res/values-as/strings.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<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="4469836075319831821">"স্পুলাৰ প্ৰিণ্ট কৰক"</string>
+ <string name="more_options_button" msgid="2243228396432556771">"অধিক বিকল্প"</string>
+ <string name="label_destination" msgid="9132510997381599275">"লক্ষ্যস্থান"</string>
+ <string name="label_copies" msgid="3634531042822968308">"প্ৰতিলিপিসমূহ"</string>
+ <string name="label_copies_summary" msgid="3861966063536529540">"প্ৰতিলিপসমূহ:"</string>
+ <string name="label_paper_size" msgid="908654383827777759">"কাগজৰ আকাৰ"</string>
+ <string name="label_paper_size_summary" msgid="5668204981332138168">"কাগজৰ আকাৰ:"</string>
+ <string name="label_color" msgid="1108690305218188969">"ৰং"</string>
+ <!-- no translation found for label_duplex (5370037254347072243) -->
+ <skip />
+ <string name="label_orientation" msgid="2853142581990496477">"দিশ"</string>
+ <string name="label_pages" msgid="7768589729282182230">"পৃষ্ঠাসমূহ"</string>
+ <!-- no translation found for destination_default_text (5422708056807065710) -->
+ <skip />
+ <string name="template_all_pages" msgid="3322235982020148762">"সকলো <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
+ <string name="template_page_range" msgid="428638530038286328">"<xliff:g id="PAGE_COUNT">%1$s</xliff:g>ৰ পৰিসৰ"</string>
+ <string name="pages_range_example" msgid="8558694453556945172">"যেনে: ১—৫, ৮, ১১—১৩"</string>
+ <string name="print_preview" msgid="8010217796057763343">"প্ৰিণ্টৰ পূৰ্বদৰ্শন"</string>
+ <string name="install_for_print_preview" msgid="6366303997385509332">"পূৰ্বদৰ্শনৰ বাবে PDF ভিউৱাৰ ইনষ্টল কৰক"</string>
+ <string name="printing_app_crashed" msgid="854477616686566398">"প্ৰিণ্টিং এপ্ ক্ৰেশ্ব হৈছে"</string>
+ <string name="generating_print_job" msgid="3119608742651698916">"প্ৰিণ্টিং প্ৰস্তুত কৰি আছে"</string>
+ <string name="save_as_pdf" msgid="5718454119847596853">"PDF ৰূপে ছেভ কৰক"</string>
+ <string name="all_printers" msgid="5018829726861876202">"সকলো প্ৰিণ্টাৰ…"</string>
+ <string name="print_dialog" msgid="32628687461331979">"প্ৰিণ্ট সংবাদ"</string>
+ <!-- no translation found for current_page_template (5145005201131935302) -->
+ <skip />
+ <string name="page_description_template" msgid="6831239682256197161">"পৃষ্ঠা <xliff:g id="PAGE_COUNT">%2$d</xliff:g>ৰ <xliff:g id="CURRENT_PAGE">%1$d</xliff:g>"</string>
+ <string name="summary_template" msgid="8899734908625669193">"সাৰাংশ, প্ৰতিলিপিসমূহ <xliff:g id="COPIES">%1$s</xliff:g>, কাগজৰ আকাৰ <xliff:g id="PAPER_SIZE">%2$s</xliff:g>"</string>
+ <string name="expand_handle" msgid="7282974448109280522">"হেণ্ডেল বিস্তাৰ কৰক"</string>
+ <string name="collapse_handle" msgid="6886637989442507451">"হেণ্ডেল সংকুচিত কৰক"</string>
+ <string name="print_button" msgid="645164566271246268">"প্ৰিণ্ট কৰক"</string>
+ <string name="savetopdf_button" msgid="2976186791686924743">"PDFৰ জৰিয়তে ছেভ কৰক"</string>
+ <string name="print_options_expanded" msgid="6944679157471691859">"প্ৰিণ্ট বিকল্পসমূহ বিস্তাৰ কৰা হ\'ল"</string>
+ <string name="print_options_collapsed" msgid="7455930445670414332">"প্ৰিণ্ট বিকল্পসমূহ সংকুচিত কৰা হ\'ল"</string>
+ <string name="search" msgid="5421724265322228497">"সন্ধান কৰক"</string>
+ <string name="all_printers_label" msgid="3178848870161526399">"সকলো প্ৰিণ্টাৰ"</string>
+ <string name="add_print_service_label" msgid="5356702546188981940">"সেৱা যোগ কৰক"</string>
+ <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"সন্ধান বাকচটো দেখুওৱা হ\'ল"</string>
+ <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"সন্ধান বাকচটো ঢাক খাই আছে"</string>
+ <string name="print_add_printer" msgid="1088656468360653455">"প্ৰিণ্টাৰ যোগ কৰক"</string>
+ <string name="print_select_printer" msgid="7388760939873368698">"প্ৰিণ্টাৰ বাছনি কৰক"</string>
+ <string name="print_forget_printer" msgid="5035287497291910766">"প্ৰিণ্টাৰ পাহৰি যাওক"</string>
+ <!-- no translation found for print_search_result_count_utterance (6997663738361080868) -->
+ <!-- no translation found for printer_extended_description_template (1366699227703381874) -->
+ <skip />
+ <!-- no translation found for printer_info_desc (7181988788991581654) -->
+ <skip />
+ <!-- no translation found for notification_channel_progress (872788690775721436) -->
+ <skip />
+ <!-- no translation found for notification_channel_failure (9042250774797916414) -->
+ <skip />
+ <!-- no translation found for could_not_create_file (3425025039427448443) -->
+ <skip />
+ <!-- no translation found for print_services_disabled_toast (9089060734685174685) -->
+ <skip />
+ <string name="print_searching_for_printers" msgid="6550424555079932867">"প্ৰিণ্টাৰৰ সন্ধান কৰি আছে"</string>
+ <!-- no translation found for print_no_print_services (8561247706423327966) -->
+ <skip />
+ <string name="print_no_printers" msgid="4869403323900054866">"প্ৰিণ্টাৰ পোৱা নগ\'ল"</string>
+ <!-- no translation found for cannot_add_printer (7840348733668023106) -->
+ <skip />
+ <!-- no translation found for select_to_add_printers (3800709038689830974) -->
+ <skip />
+ <!-- no translation found for enable_print_service (3482815747043533842) -->
+ <skip />
+ <!-- no translation found for enabled_services_title (7036986099096582296) -->
+ <skip />
+ <!-- no translation found for recommended_services_title (3799434882937956924) -->
+ <skip />
+ <!-- no translation found for disabled_services_title (7313253167968363211) -->
+ <skip />
+ <!-- no translation found for all_services_title (5578662754874906455) -->
+ <skip />
+ <!-- no translation found for print_services_recommendation_subtitle (5678487708807185138) -->
+ <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> প্ৰিণ্ট কৰি থকা হৈছে"</string>
+ <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> বাতিল কৰি থকা হৈছে"</string>
+ <string name="failed_notification_title_template" msgid="2256217208186530973">"প্ৰিণ্টাৰৰ আসোঁৱাহ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+ <string name="blocked_notification_title_template" msgid="1175435827331588646">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> প্ৰিণ্টাৰে অৱৰোধ কৰিছে"</string>
+ <string name="cancel" msgid="4373674107267141885">"বাতিল কৰক"</string>
+ <string name="restart" msgid="2472034227037808749">"ৰিষ্টাৰ্ট কৰক"</string>
+ <string name="no_connection_to_printer" msgid="2159246915977282728">"প্ৰিণ্টাৰ সংযোগ হৈ থকা নাই"</string>
+ <string name="reason_unknown" msgid="5507940196503246139">"অজ্ঞাত"</string>
+ <!-- no translation found for print_service_security_warning_title (2160752291246775320) -->
+ <skip />
+ <!-- no translation found for print_service_security_warning_summary (1427434625361692006) -->
+ <skip />
+ <string-array name="color_mode_labels">
+ <item msgid="7602948745415174937">"ক\'লা আৰু বগা"</item>
+ <item msgid="2762241247228983754">"ৰং"</item>
+ </string-array>
+ <string-array name="duplex_mode_labels">
+ <item msgid="3882302912790928315">"একো নাই"</item>
+ <item msgid="7296563835355641719">"দীঘল প্ৰান্ত"</item>
+ <item msgid="79513688117503758">"চুটি প্ৰান্ত"</item>
+ </string-array>
+ <string-array name="orientation_labels">
+ <item msgid="4061931020926489228">"প\'ৰ্ট্ৰেইট"</item>
+ <item msgid="3199660090246166812">"লেণ্ডস্কেইপ"</item>
+ </string-array>
+ <string name="print_write_error_message" msgid="5787642615179572543">"ফাইলত লিখিব পৰা নহ\'ল"</string>
+ <string name="print_error_default_message" msgid="8602678405502922346">"দুঃখিত, প্ৰিণ্টিঙৰ কাম নহ\'ল। পুনৰ চেষ্টা কৰক।"</string>
+ <string name="print_error_retry" msgid="1426421728784259538">"পুনৰ চেষ্টা কৰক"</string>
+ <string name="print_error_printer_unavailable" msgid="8985614415253203381">"এই প্ৰিণ্টাৰটো বৰ্তমান উপলব্ধ নহয়।"</string>
+ <!-- no translation found for print_cannot_load_page (6179560924492912009) -->
+ <skip />
+ <string name="print_preparing_preview" msgid="3939930735671364712">"পূৰ্বদৰ্শন প্ৰস্তুত কৰি আছে…"</string>
+</resources>
diff --git a/packages/PrintSpooler/res/values-or/strings.xml b/packages/PrintSpooler/res/values-or/strings.xml
new file mode 100644
index 0000000..e45c091
--- /dev/null
+++ b/packages/PrintSpooler/res/values-or/strings.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<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="4469836075319831821">"ପ୍ରିଣ୍ଟ ସ୍ପୁଲର୍"</string>
+ <string name="more_options_button" msgid="2243228396432556771">"ଅଧିକ ବିକଳ୍ପ"</string>
+ <string name="label_destination" msgid="9132510997381599275">"ଲକ୍ଷ୍ୟସ୍ଥଳ"</string>
+ <string name="label_copies" msgid="3634531042822968308">"କପୀଗୁଡ଼ିକ"</string>
+ <string name="label_copies_summary" msgid="3861966063536529540">"କପୀଗୁଡ଼ିକ:"</string>
+ <string name="label_paper_size" msgid="908654383827777759">"କାଗଜର ଆକାର"</string>
+ <string name="label_paper_size_summary" msgid="5668204981332138168">"କାଗଜର ଆକାର:"</string>
+ <string name="label_color" msgid="1108690305218188969">"ରଙ୍ଗ"</string>
+ <!-- no translation found for label_duplex (5370037254347072243) -->
+ <skip />
+ <string name="label_orientation" msgid="2853142581990496477">"ଓରିଏଣ୍ଟେଶନ୍"</string>
+ <!-- no translation found for label_pages (7768589729282182230) -->
+ <skip />
+ <!-- no translation found for destination_default_text (5422708056807065710) -->
+ <skip />
+ <!-- no translation found for template_all_pages (3322235982020148762) -->
+ <skip />
+ <!-- no translation found for template_page_range (428638530038286328) -->
+ <skip />
+ <string name="pages_range_example" msgid="8558694453556945172">"ଯେପରିକି 1—5,8,11—13"</string>
+ <string name="print_preview" msgid="8010217796057763343">"ପ୍ରିଣ୍ଟ ଝଲକ"</string>
+ <string name="install_for_print_preview" msgid="6366303997385509332">"ଝଲକ ଦେଖିବା ପାଇଁ PDF ଭ୍ୟୁଅର୍ ଇନଷ୍ଟଲ୍ କରନ୍ତୁ"</string>
+ <string name="printing_app_crashed" msgid="854477616686566398">"ପ୍ରିଣ୍ଟିଙ୍ଗ ଆପ୍ କ୍ରାଶ୍ ହୋଇଗଲା"</string>
+ <string name="generating_print_job" msgid="3119608742651698916">"ପ୍ରିଣ୍ଟ ଜବ୍ ତିଆରି କରାଯାଉଛି"</string>
+ <string name="save_as_pdf" msgid="5718454119847596853">"PDF ଭାବରେ ସେଭ୍ କରନ୍ତୁ"</string>
+ <string name="all_printers" msgid="5018829726861876202">"ସମସ୍ତ ପ୍ରିଣ୍ଟର୍…"</string>
+ <string name="print_dialog" msgid="32628687461331979">"ପ୍ରିଣ୍ଟ ଡାୟଲଗ୍"</string>
+ <!-- no translation found for current_page_template (5145005201131935302) -->
+ <skip />
+ <!-- no translation found for page_description_template (6831239682256197161) -->
+ <skip />
+ <!-- no translation found for summary_template (8899734908625669193) -->
+ <skip />
+ <!-- no translation found for expand_handle (7282974448109280522) -->
+ <skip />
+ <!-- no translation found for collapse_handle (6886637989442507451) -->
+ <skip />
+ <string name="print_button" msgid="645164566271246268">"ପ୍ରିଣ୍ଟ କରନ୍ତୁ"</string>
+ <!-- no translation found for savetopdf_button (2976186791686924743) -->
+ <skip />
+ <!-- no translation found for print_options_expanded (6944679157471691859) -->
+ <skip />
+ <!-- no translation found for print_options_collapsed (7455930445670414332) -->
+ <skip />
+ <string name="search" msgid="5421724265322228497">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+ <string name="all_printers_label" msgid="3178848870161526399">"ସମସ୍ତ ପ୍ରିଣ୍ଟର୍"</string>
+ <string name="add_print_service_label" msgid="5356702546188981940">"ସେବା ଯୋଡ଼ନ୍ତୁ"</string>
+ <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ସର୍ଚ୍ଚ ବକ୍ସ ଦେଖାଯାଇଛି"</string>
+ <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"ସର୍ଚ୍ଚ ବକ୍ସ ଲୁଚି ରହିଛି"</string>
+ <string name="print_add_printer" msgid="1088656468360653455">"ପ୍ରିଣ୍ଟର୍ ଯୋଡ଼ନ୍ତୁ"</string>
+ <string name="print_select_printer" msgid="7388760939873368698">"ପ୍ରିଣ୍ଟର୍ ଚୟନ କରନ୍ତୁ"</string>
+ <string name="print_forget_printer" msgid="5035287497291910766">"ପ୍ରିଣ୍ଟର୍ ଭୁଲିଯାଆନ୍ତୁ"</string>
+ <!-- no translation found for print_search_result_count_utterance (6997663738361080868) -->
+ <!-- no translation found for printer_extended_description_template (1366699227703381874) -->
+ <skip />
+ <!-- no translation found for printer_info_desc (7181988788991581654) -->
+ <skip />
+ <!-- no translation found for notification_channel_progress (872788690775721436) -->
+ <skip />
+ <!-- no translation found for notification_channel_failure (9042250774797916414) -->
+ <skip />
+ <!-- no translation found for could_not_create_file (3425025039427448443) -->
+ <skip />
+ <!-- no translation found for print_services_disabled_toast (9089060734685174685) -->
+ <skip />
+ <string name="print_searching_for_printers" msgid="6550424555079932867">"ପ୍ରିଣ୍ଟର୍ ଖୋଜାଯାଉଛି"</string>
+ <!-- no translation found for print_no_print_services (8561247706423327966) -->
+ <skip />
+ <string name="print_no_printers" msgid="4869403323900054866">"କୌଣସି ପ୍ରିଣ୍ଟର୍ ମିଳିଲା ନାହିଁ"</string>
+ <!-- no translation found for cannot_add_printer (7840348733668023106) -->
+ <skip />
+ <!-- no translation found for select_to_add_printers (3800709038689830974) -->
+ <skip />
+ <!-- no translation found for enable_print_service (3482815747043533842) -->
+ <skip />
+ <!-- no translation found for enabled_services_title (7036986099096582296) -->
+ <skip />
+ <!-- no translation found for recommended_services_title (3799434882937956924) -->
+ <skip />
+ <!-- no translation found for disabled_services_title (7313253167968363211) -->
+ <skip />
+ <!-- no translation found for all_services_title (5578662754874906455) -->
+ <skip />
+ <!-- no translation found for print_services_recommendation_subtitle (5678487708807185138) -->
+ <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ପ୍ରିଣ୍ଟ କରାଯାଉଛି"</string>
+ <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> କ୍ୟାନ୍ସଲ୍ କରାଯାଉଛି"</string>
+ <string name="failed_notification_title_template" msgid="2256217208186530973">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ପ୍ରିଣ୍ଟର୍ ତ୍ରୁଟି"</string>
+ <string name="blocked_notification_title_template" msgid="1175435827331588646">"ପ୍ରିଣ୍ଟର୍ ଦ୍ୱାରା ରୋକାଯାଇଥିବା <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+ <string name="cancel" msgid="4373674107267141885">"କ୍ୟାନ୍ସଲ୍"</string>
+ <string name="restart" msgid="2472034227037808749">"ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"</string>
+ <string name="no_connection_to_printer" msgid="2159246915977282728">"ପ୍ରିଣ୍ଟର୍କୁ କୌଣସି ସଂଯୋଗ ନାହିଁ"</string>
+ <string name="reason_unknown" msgid="5507940196503246139">"ଅଜଣା"</string>
+ <!-- no translation found for print_service_security_warning_title (2160752291246775320) -->
+ <skip />
+ <!-- no translation found for print_service_security_warning_summary (1427434625361692006) -->
+ <skip />
+ <string-array name="color_mode_labels">
+ <item msgid="7602948745415174937">"କଳା ଓ ଧଳା"</item>
+ <item msgid="2762241247228983754">"ରଙ୍ଗ"</item>
+ </string-array>
+ <string-array name="duplex_mode_labels">
+ <item msgid="3882302912790928315">"କିଛିନୁହେଁ"</item>
+ <item msgid="7296563835355641719">"ଲମ୍ବା ପ୍ରାନ୍ତ"</item>
+ <item msgid="79513688117503758">"ଛୋଟ ପ୍ରାନ୍ତ"</item>
+ </string-array>
+ <string-array name="orientation_labels">
+ <item msgid="4061931020926489228">"ପୋର୍ଟ୍ରେଟ୍"</item>
+ <item msgid="3199660090246166812">"ଲ୍ୟାଣ୍ଡସ୍କେପ୍"</item>
+ </string-array>
+ <string name="print_write_error_message" msgid="5787642615179572543">"ଫାଇଲରେ ଲେଖିପାରିଲା ନାହିଁ"</string>
+ <string name="print_error_default_message" msgid="8602678405502922346">"କାମ କଲାନାହିଁ, ପୁଣିଥରେ ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+ <string name="print_error_retry" msgid="1426421728784259538">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+ <string name="print_error_printer_unavailable" msgid="8985614415253203381">"ପ୍ରିଣ୍ଟର୍ ବର୍ତ୍ତମାନ ଉପଲବ୍ଧ ନାହିଁ।"</string>
+ <!-- no translation found for print_cannot_load_page (6179560924492912009) -->
+ <skip />
+ <string name="print_preparing_preview" msgid="3939930735671364712">"ଝଲକ ତିଆରି କରାଯାଉଛି…"</string>
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 4599640..df1a7a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -681,7 +681,7 @@
synchronized (mLock) {
if (!mInternalAccessPoints.isEmpty()) {
mInternalAccessPoints.clear();
- mListener.onAccessPointsChanged();
+ conditionallyNotifyListeners();
}
}
}
@@ -916,7 +916,7 @@
}
/**
- * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} if {@link #mStaleScanResults}
+ * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults}
* is false.
*/
private void conditionallyNotifyListeners() {
@@ -924,6 +924,6 @@
return;
}
- ThreadUtils.postOnMainThread(() -> mListener.onAccessPointsChanged());
+ mListener.onAccessPointsChanged();
}
}
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/Shell/res/values-as/strings.xml b/packages/Shell/res/values-as/strings.xml
new file mode 100644
index 0000000..8ad0358
--- /dev/null
+++ b/packages/Shell/res/values-as/strings.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<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="3701846017049540910">"শ্বেল"</string>
+ <!-- no translation found for bugreport_notification_channel (2574150205913861141) -->
+ <skip />
+ <!-- no translation found for bugreport_in_progress_title (4311705936714972757) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_title (4429132808670114081) -->
+ <skip />
+ <!-- no translation found for bugreport_updating_title (4423539949559634214) -->
+ <skip />
+ <!-- no translation found for bugreport_updating_wait (3322151947853929470) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_text (1223616207145252689) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_text (5758325479058638893) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_text (8353769438382138847) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_pending_screenshot_text (2343263822812016950) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_pending_screenshot_text (1474435374470177193) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_pending_screenshot_text (1474435374470177193) -->
+ <skip />
+ <!-- no translation found for bugreport_confirm (5917407234515812495) -->
+ <skip />
+ <!-- no translation found for bugreport_confirm_dont_repeat (6179945398364357318) -->
+ <skip />
+ <!-- no translation found for bugreport_storage_title (5332488144740527109) -->
+ <skip />
+ <!-- no translation found for bugreport_unreadable_text (586517851044535486) -->
+ <skip />
+ <!-- no translation found for bugreport_add_details_to_zip_failed (1302931926486712371) -->
+ <skip />
+ <!-- no translation found for bugreport_unnamed (2800582406842092709) -->
+ <skip />
+ <!-- no translation found for bugreport_info_action (2158204228510576227) -->
+ <skip />
+ <!-- no translation found for bugreport_screenshot_action (8677781721940614995) -->
+ <skip />
+ <!-- no translation found for bugreport_screenshot_taken (5684211273096253120) -->
+ <skip />
+ <!-- no translation found for bugreport_screenshot_failed (5853049140806834601) -->
+ <skip />
+ <!-- no translation found for bugreport_info_dialog_title (1355948594292983332) -->
+ <skip />
+ <!-- no translation found for bugreport_info_name (4414036021935139527) -->
+ <skip />
+ <!-- no translation found for bugreport_info_title (2306030793918239804) -->
+ <skip />
+ <!-- no translation found for bugreport_info_description (5072835127481627722) -->
+ <skip />
+ <!-- no translation found for save (4781509040564835759) -->
+ <skip />
+ <!-- no translation found for bugreport_intent_chooser_title (7605709494790894076) -->
+ <skip />
+</resources>
diff --git a/packages/Shell/res/values-or/strings.xml b/packages/Shell/res/values-or/strings.xml
new file mode 100644
index 0000000..750b5f73
--- /dev/null
+++ b/packages/Shell/res/values-or/strings.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<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="3701846017049540910">"ଶେଲ୍"</string>
+ <!-- no translation found for bugreport_notification_channel (2574150205913861141) -->
+ <skip />
+ <!-- no translation found for bugreport_in_progress_title (4311705936714972757) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_title (4429132808670114081) -->
+ <skip />
+ <!-- no translation found for bugreport_updating_title (4423539949559634214) -->
+ <skip />
+ <!-- no translation found for bugreport_updating_wait (3322151947853929470) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_text (1223616207145252689) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_text (5758325479058638893) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_text (8353769438382138847) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_pending_screenshot_text (2343263822812016950) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_pending_screenshot_text (1474435374470177193) -->
+ <skip />
+ <!-- no translation found for bugreport_finished_pending_screenshot_text (1474435374470177193) -->
+ <skip />
+ <!-- no translation found for bugreport_confirm (5917407234515812495) -->
+ <skip />
+ <!-- no translation found for bugreport_confirm_dont_repeat (6179945398364357318) -->
+ <skip />
+ <!-- no translation found for bugreport_storage_title (5332488144740527109) -->
+ <skip />
+ <!-- no translation found for bugreport_unreadable_text (586517851044535486) -->
+ <skip />
+ <!-- no translation found for bugreport_add_details_to_zip_failed (1302931926486712371) -->
+ <skip />
+ <!-- no translation found for bugreport_unnamed (2800582406842092709) -->
+ <skip />
+ <!-- no translation found for bugreport_info_action (2158204228510576227) -->
+ <skip />
+ <!-- no translation found for bugreport_screenshot_action (8677781721940614995) -->
+ <skip />
+ <!-- no translation found for bugreport_screenshot_taken (5684211273096253120) -->
+ <skip />
+ <!-- no translation found for bugreport_screenshot_failed (5853049140806834601) -->
+ <skip />
+ <!-- no translation found for bugreport_info_dialog_title (1355948594292983332) -->
+ <skip />
+ <!-- no translation found for bugreport_info_name (4414036021935139527) -->
+ <skip />
+ <!-- no translation found for bugreport_info_title (2306030793918239804) -->
+ <skip />
+ <!-- no translation found for bugreport_info_description (5072835127481627722) -->
+ <skip />
+ <!-- no translation found for save (4781509040564835759) -->
+ <skip />
+ <!-- no translation found for bugreport_intent_chooser_title (7605709494790894076) -->
+ <skip />
+</resources>
diff --git a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
index 1bb36fb..0734e0d 100644
--- a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
+++ b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
@@ -60,7 +60,7 @@
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
final RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT);
- row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED);
+ row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
row.add(Root.COLUMN_ICON, android.R.mipmap.sym_def_app_icon);
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.bugreport_storage_title));
row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index 4b3afdc..6c31b2a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -181,6 +181,5 @@
void onScreenOff();
void onShowSafetyWarning(int flags);
void onAccessibilityModeChanged(Boolean showA11yStream);
- void onConnectedDeviceChanged(String deviceName);
}
}
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/drawable/rounded_bg_bottom_background.xml b/packages/SystemUI/res/drawable/rounded_bg_bottom_background.xml
new file mode 100644
index 0000000..8d03ce7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_bg_bottom_background.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@*android:color/material_grey_200" />
+ <corners
+ android:bottomLeftRadius="@dimen/corner_size"
+ android:topLeftRadius="0dp"
+ android:bottomRightRadius="@dimen/corner_size"
+ android:topRightRadius="0dp"
+ />
+</shape>
diff --git a/packages/SystemUI/res/layout/output_chooser.xml b/packages/SystemUI/res/layout/output_chooser.xml
deleted file mode 100644
index b9f7b15..0000000
--- a/packages/SystemUI/res/layout/output_chooser.xml
+++ /dev/null
@@ -1,70 +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.
--->
-<!-- extends LinearLayout -->
-<com.android.systemui.HardwareUiLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginBottom="0dp"
- android:clipToPadding="false"
- android:theme="@style/qs_theme"
- android:clipChildren="false">
- <com.android.systemui.volume.OutputChooserLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
- android:id="@+id/output_chooser"
- android:layout_width="@dimen/output_chooser_panel_width"
- android:layout_height="@dimen/output_chooser_panel_width"
- android:layout_gravity="center_vertical|end"
- android:orientation="vertical"
- android:translationZ="8dp"
- android:padding="20dp" >
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textDirection="locale"
- android:textAppearance="@style/TextAppearance.QS.DetailHeader"
- android:layout_marginBottom="20dp" />
-
- <com.android.systemui.qs.AutoSizingList
- android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- sysui:itemHeight="@dimen/qs_detail_item_height"
- style="@style/AutoSizingList"/>
-
- <LinearLayout
- android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:gravity="center"
- android:orientation="vertical">
-
- <TextView
- android:id="@+id/empty_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textDirection="locale"
- android:layout_marginTop="20dp"
- android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
- </LinearLayout>
- </com.android.systemui.volume.OutputChooserLayout>
-</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/layout/output_chooser_item.xml b/packages/SystemUI/res/layout/output_chooser_item.xml
deleted file mode 100644
index c3ddbbe..0000000
--- a/packages/SystemUI/res/layout/output_chooser_item.xml
+++ /dev/null
@@ -1,72 +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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/qs_detail_item_height"
- android:background="@drawable/btn_borderless_rect"
- android:clickable="true"
- android:focusable="true"
- android:gravity="center_vertical"
- android:orientation="horizontal" >
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="@dimen/qs_detail_item_icon_width"
- android:layout_height="@dimen/qs_detail_item_icon_size"
- android:layout_marginStart="@dimen/qs_detail_item_icon_marginStart"
- android:layout_marginEnd="@dimen/qs_detail_item_icon_marginEnd"
- android:background="?android:selectableItemBackgroundBorderless"
- android:tint="?android:attr/textColorPrimary"/>
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginStart="12dp"
- android:layout_weight="1"
- android:orientation="vertical" >
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textDirection="locale"
- android:ellipsize="end"
- android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" />
-
- <TextView
- android:id="@android:id/summary"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textDirection="locale"
- android:layout_marginTop="2dp"
- android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
- </LinearLayout>
-
- <ImageView
- android:id="@android:id/icon2"
- style="@style/QSBorderlessButton"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="30dp"
- android:clickable="true"
- android:focusable="true"
- android:scaleType="center"
- android:contentDescription="@*android:string/media_route_controller_disconnect"
- android:tint="?android:attr/textColorPrimary" />
-
-</LinearLayout>
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/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 803659f..b6d241b 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -23,79 +23,70 @@
<!-- right-aligned to be physically near volume button -->
<LinearLayout
android:id="@+id/volume_dialog"
+ android:minWidth="@dimen/volume_dialog_panel_width"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
- android:minWidth="@dimen/volume_dialog_panel_width"
android:background="@android:color/transparent"
android:layout_margin="@dimen/volume_dialog_base_margin"
android:orientation="vertical"
android:clipChildren="false" >
- <LinearLayout
- android:id="@+id/volume_dialog_rows"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:background="@drawable/rounded_bg_full"
- android:translationZ="@dimen/volume_panel_elevation"
- android:orientation="horizontal" >
- <!-- volume rows added and removed here! :-) -->
- </LinearLayout>
-
<FrameLayout
- android:id="@+id/footer"
- android:layout_width="@dimen/volume_dialog_panel_width"
- android:layout_height="@dimen/volume_dialog_panel_width"
- android:layout_marginTop="6dp"
- android:layout_marginBottom="6dp"
- android:layout_below="@id/volume_dialog_rows"
+ android:id="@+id/ringer"
+ android:layout_width="@dimen/volume_dialog_ringer_size"
+ android:layout_height="@dimen/volume_dialog_ringer_size"
+ android:layout_marginBottom="@dimen/volume_dialog_spacer"
+ android:elevation="@dimen/volume_panel_elevation"
+ android:layout_gravity="right"
android:background="@drawable/rounded_bg_full">
-
- <LinearLayout
- android:id="@+id/footer_linear_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:gravity="center"
- android:layout_gravity="end"
- android:translationZ="@dimen/volume_panel_elevation"
- android:clickable="true"
- android:orientation="vertical" >
-
- <TextView
- android:id="@+id/ringer_title"
- android:text="@string/ring_toggle_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLines="1"
- android:layout_centerVertical="true"
- android:textColor="?android:attr/colorControlNormal"
- android:textAppearance="@style/TextAppearance.Volume.Header" />
-
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/ringer_icon"
- style="@style/VolumeButtons"
- android:background="?android:selectableItemBackgroundBorderless"
- android:layout_width="@dimen/volume_dialog_panel_width"
- android:layout_height="@dimen/volume_button_size"
- android:tint="@color/accent_tint_color_selector"
- android:soundEffectsEnabled="false" />
-
- <TextView
- android:id="@+id/ringer_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLines="1"
- android:textColor="?android:attr/colorControlNormal"
- android:textAppearance="@style/TextAppearance.Volume.Header.Secondary" />
- </LinearLayout>
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/ringer_icon"
+ style="@style/VolumeButtons"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:layout_width="@dimen/volume_dialog_tap_target_size"
+ android:layout_height="@dimen/volume_dialog_tap_target_size"
+ android:tint="@color/accent_tint_color_selector"
+ android:layout_gravity="center"
+ android:soundEffectsEnabled="false" />
<include layout="@layout/volume_dnd_icon"/>
</FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/main"
+ android:layout_width="wrap_content"
+ android:minWidth="@dimen/volume_dialog_panel_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:background="@drawable/rounded_bg_full"
+ android:elevation="@dimen/volume_panel_elevation" >
+ <LinearLayout
+ android:id="@+id/volume_dialog_rows"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/volume_dialog_panel_width"
+ android:gravity="center"
+ android:orientation="horizontal" >
+ <!-- volume rows added and removed here! :-) -->
+ </LinearLayout>
+ <FrameLayout
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:background="@drawable/rounded_bg_bottom_background">
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/settings"
+ android:src="@drawable/ic_settings"
+ android:layout_width="@dimen/volume_dialog_tap_target_size"
+ android:layout_height="@dimen/volume_dialog_tap_target_size"
+ android:layout_gravity="center"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:tint="#8A000000"
+ android:soundEffectsEnabled="false" />
+ </FrameLayout>
+ </LinearLayout>
+
</LinearLayout>
</com.android.systemui.volume.VolumeUiLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index fb9355a..def6f6b 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -18,78 +18,37 @@
android:tag="row"
android:layout_height="wrap_content"
android:layout_width="@dimen/volume_dialog_panel_width"
- android:clipChildren="true"
- android:clipToPadding="true"
+ android:clipChildren="false"
+ android:clipToPadding="false"
android:theme="@style/qs_theme">
<LinearLayout
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp"
+ android:layout_marginTop="@dimen/volume_dialog_slider_margin_top"
android:gravity="center"
+ android:layout_gravity="center"
android:orientation="vertical" >
-
- <LinearLayout
- android:orientation="vertical"
+ <TextView
+ android:id="@+id/volume_row_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:padding="5dp">
- <TextView
- android:id="@+id/volume_row_header"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLength="10"
- android:maxLines="1"
- android:textColor="?android:attr/colorControlNormal"
- android:textAppearance="@style/TextAppearance.Volume.Header" />
- <LinearLayout
- android:id="@+id/output_chooser"
- android:orientation="vertical"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minWidth="48dp"
- android:minHeight="48dp"
- android:paddingTop="10dp"
- android:background="?android:selectableItemBackgroundBorderless"
- android:gravity="center">
- <TextView
- android:id="@+id/volume_row_connected_device"
- android:visibility="gone"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLength="10"
- android:ellipsize="end"
- android:maxLines="1"
- android:textAppearance="@style/TextAppearance.Volume.Header.Secondary" />
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/output_chooser_button"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:background="?android:selectableItemBackgroundBorderless"
- android:contentDescription="@string/accessibility_output_chooser"
- style="@style/VolumeButtons"
- android:clickable="false"
- android:layout_centerVertical="true"
- android:src="@drawable/ic_swap"
- android:soundEffectsEnabled="false"/>
- </LinearLayout>
- </LinearLayout>
+ android:ellipsize="end"
+ android:maxLength="10"
+ android:maxLines="1"
+ android:visibility="gone"
+ android:textColor="?android:attr/colorControlNormal"
+ android:textAppearance="@style/TextAppearance.Volume.Header" />
<FrameLayout
android:id="@+id/volume_row_slider_frame"
- android:padding="0dp"
- android:layout_width="@dimen/volume_dialog_panel_width"
+ android:layout_width="match_parent"
android:layoutDirection="rtl"
- android:layout_height="@dimen/volume_dialog_panel_width">
+ android:layout_height="@dimen/volume_dialog_slider_height">
<SeekBar
android:id="@+id/volume_row_slider"
android:clickable="true"
- android:padding="0dp"
- android:layout_margin="0dp"
- android:layout_width="@dimen/volume_dialog_panel_width"
- android:layout_height="@dimen/volume_dialog_panel_width"
+ android:layout_width="@dimen/volume_dialog_slider_height"
+ android:layout_height="match_parent"
android:layoutDirection="rtl"
android:layout_gravity="center"
android:rotation="90" />
@@ -98,10 +57,10 @@
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/volume_row_icon"
style="@style/VolumeButtons"
- android:padding="10dp"
- android:layout_width="@dimen/volume_button_size"
- android:layout_height="@dimen/volume_button_size"
+ android:layout_width="@dimen/volume_dialog_tap_target_size"
+ android:layout_height="@dimen/volume_dialog_tap_target_size"
android:background="?android:selectableItemBackgroundBorderless"
+ android:tint="@color/accent_tint_color_selector"
android:soundEffectsEnabled="false" />
</LinearLayout>
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 627b4bc..59c009f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -263,12 +263,22 @@
<dimen name="qs_panel_width">@dimen/notification_panel_width</dimen>
<!-- the amount the volume panel should be offset at the end from the view next to it (or
- the scren edge, in portrait-->
- <dimen name="volume_dialog_base_margin">12dp</dimen>
+ the screen edge, in portrait-->
+ <dimen name="volume_dialog_base_margin">8dp</dimen>
- <dimen name="volume_dialog_panel_width">100dp</dimen>
+ <dimen name="volume_dialog_panel_width">64dp</dimen>
- <dimen name="output_chooser_panel_width">320dp</dimen>
+ <dimen name="volume_dialog_slider_height">101dp</dimen>
+
+ <dimen name="volume_dialog_row_height">252dp</dimen>
+
+ <dimen name="volume_dialog_ringer_size">64dp</dimen>
+
+ <dimen name="volume_dialog_tap_target_size">48dp</dimen>
+
+ <dimen name="volume_dialog_spacer">4dp</dimen>
+
+ <dimen name="volume_dialog_slider_margin_top">13dp</dimen>
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
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 71a3ac5..920dd98 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1346,6 +1346,10 @@
<string name="volume_dialog_title">%s volume controls</string>
+ <string name="volume_dialog_ringer_guidance_vibrate">Calls and notifications will vibrate</string>
+ <string name="volume_dialog_ringer_guidance_silent">Calls and notifications will be muted</string>
+ <string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring</string>
+
<string name="output_title">Media output</string>
<string name="output_calls_title">Phone call output</string>
<string name="output_none_found">No devices found</string>
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 8b57740..ee573fb 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -30,27 +30,28 @@
@Retention(RetentionPolicy.SOURCE)
@StringDef({
- Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
- Key.DEBUG_MODE_ENABLED,
- Key.HOTSPOT_TILE_LAST_USED,
- Key.COLOR_INVERSION_TILE_LAST_USED,
- Key.DND_TILE_VISIBLE,
- Key.DND_TILE_COMBINED_ICON,
- Key.DND_CONFIRMED_PRIORITY_INTRODUCTION,
- Key.DND_CONFIRMED_SILENCE_INTRODUCTION,
- Key.DND_FAVORITE_BUCKET_INDEX,
- Key.DND_NONE_SELECTED,
- Key.DND_FAVORITE_ZEN,
- Key.QS_HOTSPOT_ADDED,
- Key.QS_DATA_SAVER_ADDED,
- Key.QS_DATA_SAVER_DIALOG_SHOWN,
- 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,
+ Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
+ Key.DEBUG_MODE_ENABLED,
+ Key.HOTSPOT_TILE_LAST_USED,
+ Key.COLOR_INVERSION_TILE_LAST_USED,
+ Key.DND_TILE_VISIBLE,
+ Key.DND_TILE_COMBINED_ICON,
+ Key.DND_CONFIRMED_PRIORITY_INTRODUCTION,
+ Key.DND_CONFIRMED_SILENCE_INTRODUCTION,
+ Key.DND_FAVORITE_BUCKET_INDEX,
+ Key.DND_NONE_SELECTED,
+ Key.DND_FAVORITE_ZEN,
+ Key.QS_HOTSPOT_ADDED,
+ Key.QS_DATA_SAVER_ADDED,
+ Key.QS_DATA_SAVER_DIALOG_SHOWN,
+ 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,
+ Key.SEEN_RINGER_GUIDANCE_COUNT
})
public @interface Key {
@Deprecated
@@ -85,6 +86,7 @@
String SEEN_MULTI_USER = "HasSeenMultiUser";
String NUM_APPS_LAUNCHED = "NumAppsLaunched";
String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
+ String SEEN_RINGER_GUIDANCE_COUNT = "RingerGuidanceCount";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
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/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/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index d74a59e..a794e19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -461,7 +461,7 @@
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
- if (changedView == this) {
+ if (changedView == this && mOnVisibilityChangedListener != null) {
mOnVisibilityChangedListener.accept(visibility == VISIBLE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
deleted file mode 100644
index 6ed07f8..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ /dev/null
@@ -1,534 +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 com.android.systemui.volume;
-
-import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
-import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
-import static android.support.v7.media.MediaRouter.UNSELECT_REASON_DISCONNECTED;
-
-import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription;
-
-import android.app.Dialog;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.support.v7.media.MediaControlIntent;
-import android.support.v7.media.MediaRouteSelector;
-import android.support.v7.media.MediaRouter;
-import android.telecom.TelecomManager;
-import android.util.Log;
-import android.util.Pair;
-import android.view.Window;
-import android.view.WindowManager;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.settingslib.Utils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.systemui.Dependency;
-import com.android.systemui.HardwareUiLayout;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.plugins.VolumeDialogController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.List;
-
-public class OutputChooserDialog extends Dialog
- implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback {
-
- private static final String TAG = Util.logTag(OutputChooserDialog.class);
- private static final int MAX_DEVICES = 10;
-
- private static final long UPDATE_DELAY_MS = 300L;
- private static final int MSG_UPDATE_ITEMS = 1;
-
- private final Context mContext;
- private final BluetoothController mBluetoothController;
- private WifiManager mWifiManager;
- private OutputChooserLayout mView;
- private final MediaRouterWrapper mRouter;
- private final MediaRouterCallback mRouterCallback;
- private long mLastUpdateTime;
- static final boolean INCLUDE_MEDIA_ROUTES = false;
- private boolean mIsInCall;
- protected boolean isAttached;
-
- private final MediaRouteSelector mRouteSelector;
- private Drawable mDefaultIcon;
- private Drawable mTvIcon;
- private Drawable mSpeakerIcon;
- private Drawable mSpeakerGroupIcon;
- private HardwareUiLayout mHardwareLayout;
- private final VolumeDialogController mController;
-
- public OutputChooserDialog(Context context, MediaRouterWrapper router) {
- super(context, com.android.systemui.R.style.qs_theme);
- mContext = context;
- mBluetoothController = Dependency.get(BluetoothController.class);
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- TelecomManager tm = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
- mIsInCall = tm.isInCall();
- mRouter = router;
- mRouterCallback = new MediaRouterCallback();
- mRouteSelector = new MediaRouteSelector.Builder()
- .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
- .build();
-
- final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- context.registerReceiver(mReceiver, filter);
-
- mController = Dependency.get(VolumeDialogController.class);
-
- // Window initialization
- Window window = getWindow();
- window.requestFeature(Window.FEATURE_NO_TITLE);
- window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
- window.addFlags(
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
- }
-
- protected void setIsInCall(boolean inCall) {
- mIsInCall = inCall;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.output_chooser);
- setCanceledOnTouchOutside(true);
- setOnDismissListener(this::onDismiss);
-
- mView = findViewById(R.id.output_chooser);
- mHardwareLayout = HardwareUiLayout.get(mView);
- mHardwareLayout.setOutsideTouchListener(view -> dismiss());
- mHardwareLayout.setSwapOrientation(false);
- mView.setCallback(this);
-
- if (mIsInCall) {
- mView.setTitle(R.string.output_calls_title);
- } else {
- mView.setTitle(R.string.output_title);
- }
-
- mDefaultIcon = mContext.getDrawable(R.drawable.ic_cast);
- mTvIcon = mContext.getDrawable(R.drawable.ic_tv);
- mSpeakerIcon = mContext.getDrawable(R.drawable.ic_speaker);
- mSpeakerGroupIcon = mContext.getDrawable(R.drawable.ic_speaker_group);
-
- final boolean wifiOff = !mWifiManager.isWifiEnabled();
- final boolean btOff = !mBluetoothController.isBluetoothEnabled();
- if (wifiOff && btOff) {
- mView.setEmptyState(getDisabledServicesMessage(wifiOff, btOff));
- }
- // time out after 5 seconds
- mView.postDelayed(() -> updateItems(true), 5000);
- }
-
- protected void cleanUp() {}
-
-
- @Override
- protected void onStart() {
- super.onStart();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (!mIsInCall && INCLUDE_MEDIA_ROUTES) {
- mRouter.addCallback(mRouteSelector, mRouterCallback,
- MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
- }
- mBluetoothController.addCallback(mCallback);
- mController.addCallback(mControllerCallbackH, mHandler);
- isAttached = true;
- }
-
- @Override
- public void onDetachedFromWindow() {
- isAttached = false;
- mRouter.removeCallback(mRouterCallback);
- mController.removeCallback(mControllerCallbackH);
- mBluetoothController.removeCallback(mCallback);
- super.onDetachedFromWindow();
- }
-
- @Override
- public void onDismiss(DialogInterface unused) {
- mContext.unregisterReceiver(mReceiver);
- cleanUp();
- }
-
- @Override
- public void show() {
- super.show();
- Dependency.get(MetricsLogger.class).visible(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
- mHardwareLayout.setTranslationX(getAnimTranslation());
- mHardwareLayout.setAlpha(0);
- mHardwareLayout.animate()
- .alpha(1)
- .translationX(0)
- .setDuration(300)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .withEndAction(() -> getWindow().getDecorView().requestAccessibilityFocus())
- .start();
- }
-
- @Override
- public void dismiss() {
- Dependency.get(MetricsLogger.class).hidden(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
- mHardwareLayout.setTranslationX(0);
- mHardwareLayout.setAlpha(1);
- mHardwareLayout.animate()
- .alpha(0)
- .translationX(getAnimTranslation())
- .setDuration(300)
- .withEndAction(() -> super.dismiss())
- .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
- .start();
- }
-
- private float getAnimTranslation() {
- return getContext().getResources().getDimension(
- com.android.systemui.R.dimen.output_chooser_panel_width) / 2;
- }
-
- @Override
- public void onDetailItemClick(OutputChooserLayout.Item item) {
- if (item == null || item.tag == null) return;
- if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
- final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
- if (device.getMaxConnectionState() == BluetoothProfile.STATE_DISCONNECTED) {
- Dependency.get(MetricsLogger.class).action(
- MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
- mBluetoothController.connect(device);
- }
- } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
- final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag;
- if (route.isEnabled()) {
- Dependency.get(MetricsLogger.class).action(
- MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
- route.select();
- }
- }
- }
-
- @Override
- public void onDetailItemDisconnect(OutputChooserLayout.Item item) {
- if (item == null || item.tag == null) return;
- if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
- final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
- Dependency.get(MetricsLogger.class).action(
- MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
- mBluetoothController.disconnect(device);
- } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
- Dependency.get(MetricsLogger.class).action(
- MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
- mRouter.unselect(UNSELECT_REASON_DISCONNECTED);
- }
- }
-
- private void updateItems(boolean timeout) {
- if (SystemClock.uptimeMillis() - mLastUpdateTime < UPDATE_DELAY_MS) {
- mHandler.removeMessages(MSG_UPDATE_ITEMS);
- mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ITEMS, timeout),
- mLastUpdateTime + UPDATE_DELAY_MS);
- return;
- }
- mLastUpdateTime = SystemClock.uptimeMillis();
- if (mView == null) return;
- ArrayList<OutputChooserLayout.Item> items = new ArrayList<>();
-
- // Add bluetooth devices
- addBluetoothDevices(items);
-
- // Add remote displays
- if (!mIsInCall && INCLUDE_MEDIA_ROUTES) {
- addRemoteDisplayRoutes(items);
- }
-
- items.sort(ItemComparator.sInstance);
-
- if (items.size() == 0 && timeout) {
- String emptyMessage = mContext.getString(R.string.output_none_found);
- final boolean wifiOff = !mWifiManager.isWifiEnabled();
- final boolean btOff = !mBluetoothController.isBluetoothEnabled();
- if (wifiOff || btOff) {
- emptyMessage = getDisabledServicesMessage(wifiOff, btOff);
- }
- mView.setEmptyState(emptyMessage);
- }
-
- mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
- }
-
- private String getDisabledServicesMessage(boolean wifiOff, boolean btOff) {
- return mContext.getString(R.string.output_none_found_service_off,
- wifiOff && btOff ? mContext.getString(R.string.output_service_bt_wifi)
- : wifiOff ? mContext.getString(R.string.output_service_wifi)
- : mContext.getString(R.string.output_service_bt));
- }
-
- private void addBluetoothDevices(List<OutputChooserLayout.Item> items) {
- final Collection<CachedBluetoothDevice> devices = mBluetoothController.getDevices();
- if (devices != null) {
- int connectedDevices = 0;
- int count = 0;
- for (CachedBluetoothDevice device : devices) {
- if (mBluetoothController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
- final int majorClass = device.getBtClass().getMajorDeviceClass();
- if (majorClass != BluetoothClass.Device.Major.AUDIO_VIDEO
- && majorClass != BluetoothClass.Device.Major.UNCATEGORIZED) {
- continue;
- }
- final OutputChooserLayout.Item item = new OutputChooserLayout.Item();
- item.iconResId = R.drawable.ic_qs_bluetooth_on;
- item.line1 = device.getName();
- item.tag = device;
- item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
- int state = device.getMaxConnectionState();
- if (state == BluetoothProfile.STATE_CONNECTED) {
- item.iconResId = R.drawable.ic_qs_bluetooth_connected;
- int batteryLevel = device.getBatteryLevel();
- if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- Pair<Drawable, String> pair =
- getBtClassDrawableWithDescription(getContext(), device);
- item.icon = pair.first;
- item.line2 = mContext.getString(
- R.string.quick_settings_connected_battery_level,
- Utils.formatPercentage(batteryLevel));
- } else {
- item.line2 = mContext.getString(R.string.quick_settings_connected);
- }
- item.canDisconnect = true;
- items.add(connectedDevices, item);
- connectedDevices++;
- } else if (state == BluetoothProfile.STATE_CONNECTING) {
- item.iconResId = R.drawable.ic_qs_bluetooth_connecting;
- item.line2 = mContext.getString(R.string.quick_settings_connecting);
- items.add(connectedDevices, item);
- } else {
- items.add(item);
- }
- if (++count == MAX_DEVICES) {
- break;
- }
- }
- }
- }
-
- private void addRemoteDisplayRoutes(List<OutputChooserLayout.Item> items) {
- List<MediaRouter.RouteInfo> routes = mRouter.getRoutes();
- for(MediaRouter.RouteInfo route : routes) {
- if (route.isDefaultOrBluetooth() || !route.isEnabled()
- || !route.matchesSelector(mRouteSelector)) {
- continue;
- }
- final OutputChooserLayout.Item item = new OutputChooserLayout.Item();
- item.icon = getIconDrawable(route);
- item.line1 = route.getName();
- item.tag = route;
- item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
- if (route.getConnectionState() == CONNECTION_STATE_CONNECTING) {
- mContext.getString(R.string.quick_settings_connecting);
- } else {
- item.line2 = route.getDescription();
- }
-
- if (route.getConnectionState() == CONNECTION_STATE_CONNECTED) {
- item.canDisconnect = true;
- }
- items.add(item);
- }
- }
-
- private Drawable getIconDrawable(MediaRouter.RouteInfo route) {
- Uri iconUri = route.getIconUri();
- if (iconUri != null) {
- try {
- InputStream is = getContext().getContentResolver().openInputStream(iconUri);
- Drawable drawable = Drawable.createFromStream(is, null);
- if (drawable != null) {
- return drawable;
- }
- } catch (IOException e) {
- Log.w(TAG, "Failed to load " + iconUri, e);
- // Falls back.
- }
- }
- return getDefaultIconDrawable(route);
- }
-
- private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
- // If the type of the receiver device is specified, use it.
- switch (route.getDeviceType()) {
- case MediaRouter.RouteInfo.DEVICE_TYPE_TV:
- return mTvIcon;
- case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER:
- return mSpeakerIcon;
- }
-
- // Otherwise, make the best guess based on other route information.
- if (route instanceof MediaRouter.RouteGroup) {
- // Only speakers can be grouped for now.
- return mSpeakerGroupIcon;
- }
- return mDefaultIcon;
- }
-
- private final class MediaRouterCallback extends MediaRouter.Callback {
- @Override
- public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
- updateItems(false);
- }
-
- @Override
- public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
- updateItems(false);
- }
-
- @Override
- public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
- updateItems(false);
- }
-
- @Override
- public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
- updateItems(false);
- }
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- if (D.BUG) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
- cancel();
- cleanUp();
- }
- }
- };
-
- private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
- @Override
- public void onBluetoothStateChange(boolean enabled) {
- updateItems(false);
- }
-
- @Override
- public void onBluetoothDevicesChanged() {
- updateItems(false);
- }
- };
-
- static final class ItemComparator implements Comparator<OutputChooserLayout.Item> {
- public static final ItemComparator sInstance = new ItemComparator();
-
- @Override
- public int compare(OutputChooserLayout.Item lhs, OutputChooserLayout.Item rhs) {
- // Connected item(s) first
- if (lhs.canDisconnect != rhs.canDisconnect) {
- return Boolean.compare(rhs.canDisconnect, lhs.canDisconnect);
- }
- // Bluetooth items before media routes
- if (lhs.deviceType != rhs.deviceType) {
- return Integer.compare(lhs.deviceType, rhs.deviceType);
- }
- // then by name
- return lhs.line1.toString().compareToIgnoreCase(rhs.line1.toString());
- }
- }
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_UPDATE_ITEMS:
- updateItems((Boolean) message.obj);
- break;
- }
- }
- };
-
- private final VolumeDialogController.Callbacks mControllerCallbackH
- = new VolumeDialogController.Callbacks() {
- @Override
- public void onShowRequested(int reason) {
- dismiss();
- }
-
- @Override
- public void onDismissRequested(int reason) {}
-
- @Override
- public void onScreenOff() {
- dismiss();
- }
-
- @Override
- public void onStateChanged(VolumeDialogController.State state) {}
-
- @Override
- public void onLayoutDirectionChanged(int layoutDirection) {}
-
- @Override
- public void onConfigurationChanged() {}
-
- @Override
- public void onShowVibrateHint() {}
-
- @Override
- public void onShowSilentHint() {}
-
- @Override
- public void onShowSafetyWarning(int flags) {}
-
- @Override
- public void onAccessibilityModeChanged(Boolean showA11yStream) {}
-
- @Override
- public void onConnectedDeviceChanged(String deviceName) {}
- };
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
deleted file mode 100644
index d4c6f89..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
+++ /dev/null
@@ -1,256 +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 com.android.systemui.volume;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.R;
-import com.android.systemui.qs.AutoSizingList;
-
-/**
- * Limited height list of devices.
- */
-public class OutputChooserLayout extends LinearLayout {
- private static final String TAG = "OutputChooserLayout";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private final Context mContext;
- private final H mHandler = new H();
- private final Adapter mAdapter = new Adapter();
-
- private String mTag;
- private Callback mCallback;
- private boolean mItemsVisible = true;
- private AutoSizingList mItemList;
- private View mEmpty;
- private TextView mEmptyText;
- private TextView mTitle;
-
- private Item[] mItems;
-
- public OutputChooserLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- mTag = TAG;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mItemList = findViewById(android.R.id.list);
- mItemList.setVisibility(GONE);
- mItemList.setAdapter(mAdapter);
- mEmpty = findViewById(android.R.id.empty);
- mEmpty.setVisibility(GONE);
- mEmptyText = mEmpty.findViewById(R.id.empty_text);
- mTitle = findViewById(R.id.title);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- FontSizeUtils.updateFontSize(mEmptyText, R.dimen.qs_detail_empty_text_size);
- int count = mItemList.getChildCount();
- for (int i = 0; i < count; i++) {
- View item = mItemList.getChildAt(i);
- FontSizeUtils.updateFontSize(item, R.id.empty_text,
- R.dimen.qs_detail_item_primary_text_size);
- FontSizeUtils.updateFontSize(item, android.R.id.summary,
- R.dimen.qs_detail_item_secondary_text_size);
- FontSizeUtils.updateFontSize(item, android.R.id.title,
- R.dimen.qs_detail_header_text_size);
- }
- }
-
- public void setTitle(int title) {
- mTitle.setText(title);
- }
-
- public void setEmptyState(String text) {
- mEmptyText.setText(text);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (DEBUG) Log.d(mTag, "onAttachedToWindow");
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
- mCallback = null;
- }
-
- public void setCallback(Callback callback) {
- mHandler.removeMessages(H.SET_CALLBACK);
- mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
- }
-
- public void setItems(Item[] items) {
- mHandler.removeMessages(H.SET_ITEMS);
- mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget();
- }
-
- public void setItemsVisible(boolean visible) {
- mHandler.removeMessages(H.SET_ITEMS_VISIBLE);
- mHandler.obtainMessage(H.SET_ITEMS_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
- }
-
- private void handleSetCallback(Callback callback) {
- mCallback = callback;
- }
-
- private void handleSetItems(Item[] items) {
- final int itemCount = items != null ? items.length : 0;
- mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
- mItemList.setVisibility(itemCount == 0 ? GONE : VISIBLE);
- mItems = items;
- mAdapter.notifyDataSetChanged();
- }
-
- private void handleSetItemsVisible(boolean visible) {
- if (mItemsVisible == visible) return;
- mItemsVisible = visible;
- for (int i = 0; i < mItemList.getChildCount(); i++) {
- mItemList.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
- }
- }
-
- private class Adapter extends BaseAdapter {
-
- @Override
- public int getCount() {
- return mItems != null ? mItems.length : 0;
- }
-
- @Override
- public Object getItem(int position) {
- return mItems[position];
- }
-
- @Override
- public long getItemId(int position) {
- return 0;
- }
-
- @Override
- public View getView(int position, View view, ViewGroup parent) {
- final Item item = mItems[position];
- if (view == null) {
- view = LayoutInflater.from(mContext).inflate(R.layout.output_chooser_item, parent,
- false);
- }
- view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
- final ImageView iv = view.findViewById(android.R.id.icon);
- if (item.icon != null) {
- iv.setImageDrawable(item.icon);
- } else {
- iv.setImageResource(item.iconResId);
- }
- final TextView title = view.findViewById(android.R.id.title);
- title.setText(item.line1);
- final TextView summary = view.findViewById(android.R.id.summary);
- final boolean twoLines = !TextUtils.isEmpty(item.line2);
- title.setMaxLines(twoLines ? 1 : 2);
- summary.setVisibility(twoLines ? VISIBLE : GONE);
- summary.setText(twoLines ? item.line2 : null);
- view.setOnClickListener(v -> {
- if (mCallback != null) {
- mCallback.onDetailItemClick(item);
- }
- });
-
- final ImageView icon2 = view.findViewById(android.R.id.icon2);
- if (item.canDisconnect) {
- icon2.setImageResource(R.drawable.ic_qs_cancel);
- icon2.setVisibility(VISIBLE);
- icon2.setClickable(true);
- icon2.setOnClickListener(v -> {
- if (mCallback != null) {
- mCallback.onDetailItemDisconnect(item);
- }
- });
- } else if (item.icon2 != -1) {
- icon2.setVisibility(VISIBLE);
- icon2.setImageResource(item.icon2);
- icon2.setClickable(false);
- } else {
- icon2.setVisibility(GONE);
- }
-
- return view;
- }
- };
-
- private class H extends Handler {
- private static final int SET_ITEMS = 1;
- private static final int SET_CALLBACK = 2;
- private static final int SET_ITEMS_VISIBLE = 3;
-
- public H() {
- super(Looper.getMainLooper());
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == SET_ITEMS) {
- handleSetItems((Item[]) msg.obj);
- } else if (msg.what == SET_CALLBACK) {
- handleSetCallback((OutputChooserLayout.Callback) msg.obj);
- } else if (msg.what == SET_ITEMS_VISIBLE) {
- handleSetItemsVisible(msg.arg1 != 0);
- }
- }
- }
-
- public static class Item {
- public static int DEVICE_TYPE_BT = 1;
- public static int DEVICE_TYPE_MEDIA_ROUTER = 2;
- public int iconResId;
- public Drawable icon;
- public Drawable overlay;
- public CharSequence line1;
- public CharSequence line2;
- public Object tag;
- public boolean canDisconnect;
- public int icon2 = -1;
- public int deviceType = 0;
- }
-
- public interface Callback {
- void onDetailItemClick(Item item);
- void onDetailItemDisconnect(Item item);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 3c29b77..7c71b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -110,11 +110,7 @@
private boolean mShowA11yStream;
private boolean mShowVolumeDialog;
private boolean mShowSafetyWarning;
- private DeviceCallback mDeviceCallback = new DeviceCallback();
private final NotificationManager mNotificationManager;
- @GuardedBy("mLock")
- private List<AudioDeviceInfo> mConnectedDevices = new ArrayList<>();
- private Object mLock = new Object();
private boolean mDestroyed;
private VolumePolicy mVolumePolicy;
@@ -192,7 +188,6 @@
} catch (SecurityException e) {
Log.w(TAG, "No access to media sessions", e);
}
- mAudio.registerAudioDeviceCallback(mDeviceCallback, mWorker);
}
public void setVolumePolicy(VolumePolicy policy) {
@@ -218,7 +213,6 @@
mMediaSessions.destroy();
mObserver.destroy();
mReceiver.destroy();
- mAudio.unregisterAudioDeviceCallback(mDeviceCallback);
mWorkerThread.quitSafely();
}
@@ -842,18 +836,6 @@
});
}
}
-
- @Override
- public void onConnectedDeviceChanged(String deviceName) {
- for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
- entry.getValue().post(new Runnable() {
- @Override
- public void run() {
- entry.getKey().onConnectedDeviceChanged(deviceName);
- }
- });
- }
- }
}
@@ -1060,33 +1042,6 @@
}
}
- protected final class DeviceCallback extends AudioDeviceCallback {
- public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
- synchronized (mLock) {
- for (AudioDeviceInfo info : addedDevices) {
- if (info.isSink()
- && (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
- || info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO)) {
- mConnectedDevices.add(info);
- mCallbacks.onConnectedDeviceChanged(info.getProductName().toString());
- }
- }
- }
- }
-
- public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
- synchronized (mLock) {
- for (AudioDeviceInfo info : removedDevices) {
- mConnectedDevices.remove(info);
- }
-
- if (mConnectedDevices.size() == 0) {
- mCallbacks.onConnectedDeviceChanged(null);
- }
- }
- }
- }
-
public interface UserActivityListener {
void onUserActivity();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1e8e98c..90a9fc8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -18,22 +18,22 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+import static android.media.AudioManager.RINGER_MODE_MAX;
+import static android.media.AudioManager.RINGER_MODE_NORMAL;
+import static android.media.AudioManager.RINGER_MODE_SILENT;
+import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_ACCESSIBILITY;
-import static com.android.systemui.volume.Events.DISMISS_REASON_OUTPUT_CHOOSER;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
-import static com.android.systemui.volume.Events.DISMISS_REASON_TOUCH_OUTSIDE;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.ObjectAnimator;
-import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
@@ -48,9 +48,7 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.provider.Settings.Global;
-import android.support.v7.media.MediaRouter;
import android.text.InputFilter;
-import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -60,7 +58,6 @@
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
@@ -72,9 +69,11 @@
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -108,18 +107,16 @@
private CustomDialog mDialog;
private ViewGroup mDialogView;
private ViewGroup mDialogRowsView;
- private ViewGroup mFooter;
+ private ViewGroup mRinger;
private ImageButton mRingerIcon;
+ private ImageButton mSettingsIcon;
private ImageView mZenIcon;
- private TextView mRingerStatus;
- private TextView mRingerTitle;
private final List<VolumeRow> mRows = new ArrayList<>();
private ConfigurableTexts mConfigurableTexts;
private final SparseBooleanArray mDynamic = new SparseBooleanArray();
private final KeyguardManager mKeyguard;
private final AccessibilityManagerWrapper mAccessibilityMgr;
private final Object mSafetyWarningLock = new Object();
- private final Object mOutputChooserLock = new Object();
private final Accessibility mAccessibility = new Accessibility();
private final ColorStateList mActiveSliderTint;
private final ColorStateList mInactiveSliderTint;
@@ -133,7 +130,6 @@
private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
private State mState;
private SafetyWarningDialog mSafetyWarning;
- private OutputChooserDialog mOutputChooserDialog;
private boolean mHovering = false;
public VolumeDialogImpl(Context context) {
@@ -215,11 +211,10 @@
uiLayout.updateRotation();
mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows);
- mFooter = mDialog.findViewById(R.id.footer);
- mRingerIcon = mFooter.findViewById(R.id.ringer_icon);
- mRingerStatus = mFooter.findViewById(R.id.ringer_status);
- mRingerTitle = mFooter.findViewById(R.id.ringer_title);
- mZenIcon = mFooter.findViewById(R.id.dnd_icon);
+ mRinger = mDialog.findViewById(R.id.ringer);
+ mRingerIcon = mRinger.findViewById(R.id.ringer_icon);
+ mZenIcon = mRinger.findViewById(R.id.dnd_icon);
+ mSettingsIcon = mDialog.findViewById(R.id.settings);
if (mRows.isEmpty()) {
addRow(AudioManager.STREAM_MUSIC,
@@ -244,6 +239,7 @@
updateRowsH(getActiveRow());
initRingerH();
+ initSettingsH();
}
protected ViewGroup getDialogView() {
@@ -358,10 +354,6 @@
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.anim = null;
- row.outputChooser = row.view.findViewById(R.id.output_chooser);
- row.outputChooser.setOnClickListener(mClickOutputChooser);
- row.connectedDevice = row.view.findViewById(R.id.volume_row_connected_device);
-
row.icon = row.view.findViewById(R.id.volume_row_icon);
row.icon.setImageResource(iconRes);
if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) {
@@ -396,6 +388,15 @@
}
}
+ public void initSettingsH() {
+ mSettingsIcon.setOnClickListener(v -> {
+ Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ dismissH(DISMISS_REASON_SETTINGS_CLICKED);
+ Dependency.get(ActivityStarter.class).startActivity(intent, true /* dismissShade */);
+ });
+ }
+
public void initRingerH() {
mRingerIcon.setOnClickListener(v -> {
Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, AudioManager.STREAM_RING,
@@ -406,34 +407,55 @@
}
// normal -> vibrate -> silent -> normal (skip vibrate if device doesn't have
// a vibrator.
+ int newRingerMode;
final boolean hasVibrator = mController.hasVibrator();
if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
if (hasVibrator) {
mController.vibrate();
- mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
+ newRingerMode = AudioManager.RINGER_MODE_VIBRATE;
} else {
- mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
+ newRingerMode = AudioManager.RINGER_MODE_SILENT;
}
} else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
- mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
+ newRingerMode = AudioManager.RINGER_MODE_SILENT;
} else {
- mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
+ newRingerMode = AudioManager.RINGER_MODE_NORMAL;
if (ss.level == 0) {
mController.setStreamVolume(AudioManager.STREAM_RING, 1);
}
}
updateRingerH();
- });
- mRingerIcon.setOnLongClickListener(v -> {
- Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- dismissH(DISMISS_REASON_SETTINGS_CLICKED);
- Dependency.get(ActivityStarter.class).startActivity(intent, true /* dismissShade */);
- return true;
+
+ mController.setRingerMode(newRingerMode, false);
+ maybeShowToastH(newRingerMode);
});
updateRingerH();
}
+ private void maybeShowToastH(int newRingerMode) {
+ int seenToastCount = Prefs.getInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, 0);
+
+ if (seenToastCount > VolumePrefs.SHOW_RINGER_TOAST_COUNT) {
+ return;
+ }
+ int toastText;
+ switch (newRingerMode) {
+ case RINGER_MODE_NORMAL:
+ toastText = R.string.volume_dialog_ringer_guidance_ring;
+ break;
+ case RINGER_MODE_SILENT:
+ toastText = R.string.volume_dialog_ringer_guidance_silent;
+ break;
+ case RINGER_MODE_VIBRATE:
+ default:
+ toastText = R.string.volume_dialog_ringer_guidance_vibrate;
+ }
+
+ Toast.makeText(mContext, toastText, Toast.LENGTH_SHORT).show();
+ seenToastCount++;
+ Prefs.putInt(mContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, seenToastCount);
+ }
+
public void show(int reason) {
mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
}
@@ -501,15 +523,6 @@
}
}
- private boolean isAttached() {
- return mDialogView != null && mDialogView.isAttachedToWindow();
- }
-
- private boolean hasTouchFeature() {
- final PackageManager pm = mContext.getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
- }
-
private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) {
boolean isActive = row == activeRow;
if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
@@ -536,20 +549,12 @@
final boolean isActive = row == activeRow;
final boolean shouldBeVisible = shouldBeVisibleH(row, activeRow);
Util.setVisOrGone(row.view, shouldBeVisible);
- Util.setVisOrGone(row.header, shouldBeVisible);
if (row.view.isShown()) {
updateVolumeRowSliderTintH(row, isActive);
}
}
}
- protected void updateConnectedDeviceH(String deviceName) {
- for (final VolumeRow row : mRows) {
- row.connectedDevice.setText(deviceName);
- Util.setVisOrGone(row.connectedDevice, !TextUtils.isEmpty(deviceName));
- }
- }
-
protected void updateRingerH() {
if (mState != null) {
final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
@@ -560,12 +565,10 @@
enableRingerViewsH(mState.zenMode == Global.ZEN_MODE_OFF || !mState.disallowRinger);
switch (mState.ringerModeInternal) {
case AudioManager.RINGER_MODE_VIBRATE:
- mRingerStatus.setText(R.string.volume_ringer_status_vibrate);
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
break;
case AudioManager.RINGER_MODE_SILENT:
- mRingerStatus.setText(R.string.volume_ringer_status_silent);
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
mRingerIcon.setContentDescription(mContext.getString(
R.string.volume_stream_content_description_unmute,
@@ -576,14 +579,12 @@
default:
boolean muted = (mAutomute && ss.level == 0) || ss.muted;
if (muted) {
- mRingerStatus.setText(R.string.volume_ringer_status_silent);
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
mRingerIcon.setContentDescription(mContext.getString(
R.string.volume_stream_content_description_unmute,
getStreamLabelH(ss)));
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
} else {
- mRingerStatus.setText(R.string.volume_ringer_status_normal);
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
if (mController.hasVibrator()) {
mRingerIcon.setContentDescription(mContext.getString(
@@ -603,12 +604,11 @@
}
/**
- * Toggles enable state of views in a VolumeRow (not including seekbar, outputChooser or icon)
+ * Toggles enable state of views in a VolumeRow (not including seekbar or icon)
* Hides/shows zen icon
* @param enable whether to enable volume row views and hide dnd icon
*/
private void enableVolumeRowViewsH(VolumeRow row, boolean enable) {
- row.header.setEnabled(enable);
row.dndIcon.setVisibility(enable ? View.GONE : View.VISIBLE);
}
@@ -618,8 +618,6 @@
* @param enable whether to enable ringer views and hide dnd icon
*/
private void enableRingerViewsH(boolean enable) {
- mRingerTitle.setEnabled(enable);
- mRingerStatus.setEnabled(enable);
mRingerIcon.setEnabled(enable);
mZenIcon.setVisibility(enable ? View.GONE : View.VISIBLE);
}
@@ -889,24 +887,6 @@
rescheduleTimeoutH();
}
- private void showOutputChooserH() {
- synchronized (mOutputChooserLock) {
- if (mOutputChooserDialog != null) {
- return;
- }
- mOutputChooserDialog = new OutputChooserDialog(mContext,
- new MediaRouterWrapper(MediaRouter.getInstance(mContext))) {
- @Override
- protected void cleanUp() {
- synchronized (mOutputChooserLock) {
- mOutputChooserDialog = null;
- }
- }
- };
- mOutputChooserDialog.show();
- }
- }
-
private String getStreamLabelH(StreamState ss) {
if (ss.remoteLabel != null) {
return ss.remoteLabel;
@@ -919,14 +899,6 @@
}
}
- private final OnClickListener mClickOutputChooser = new OnClickListener() {
- @Override
- public void onClick(View v) {
- dismissH(DISMISS_REASON_OUTPUT_CHOOSER);
- showOutputChooserH();
- }
- };
-
private final VolumeDialogController.Callbacks mControllerCallbackH
= new VolumeDialogController.Callbacks() {
@Override
@@ -991,11 +963,6 @@
}
}
-
- @Override
- public void onConnectedDeviceChanged(String deviceName) {
- updateConnectedDeviceH(deviceName);
- }
};
private final class H extends Handler {
@@ -1184,8 +1151,6 @@
private ObjectAnimator anim; // slider progress animation for non-touch-related updates
private int animTargetProgress;
private int lastAudibleLevel = 1;
- private View outputChooser;
- private TextView connectedDevice;
private ImageView dndIcon;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java
index 04339eb..173400f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePrefs.java
@@ -43,6 +43,8 @@
public static final String PREF_ADJUST_ALARMS = "pref_adjust_alarms";
public static final String PREF_ADJUST_NOTIFICATION = "pref_adjust_notification";
+ public static final int SHOW_RINGER_TOAST_COUNT = 9;
+
public static final boolean DEFAULT_SHOW_HEADERS = true;
public static final boolean DEFAULT_ENABLE_AUTOMUTE = true;
public static final boolean DEFAULT_ENABLE_SILENT_MODE = true;
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 63920a4..7d4d31e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -24,6 +24,7 @@
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
@@ -88,4 +89,10 @@
assertEquals(RemoteInput.SOURCE_FREE_FORM_INPUT,
RemoteInput.getResultsSource(resultIntent));
}
+
+ @Test
+ public void testNoCrashWithoutVisibilityListener() {
+ mView.setOnVisibilityChangedListener(null);
+ mView.onVisibilityChanged(mView, View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
deleted file mode 100644
index 922bde7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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.volume;
-
-import static com.android.systemui.volume.OutputChooserDialog.INCLUDE_MEDIA_ROUTES;
-
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.bluetooth.BluetoothProfile;
-import android.net.wifi.WifiManager;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v7.media.MediaRouter;
-import android.telecom.TelecomManager;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.widget.TextView;
-
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.VolumeDialogController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@Ignore
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class OutputChooserDialogTest extends SysuiTestCase {
-
- OutputChooserDialog mDialog;
-
- @Mock
- private VolumeDialogController mVolumeController;
- @Mock
- private BluetoothController mController;
- @Mock
- private WifiManager mWifiManager;
- @Mock
- private TelecomManager mTelecomManager;
-
- @Mock
- private MediaRouterWrapper mRouter;
-
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mVolumeController = mDependency.injectMockDependency(VolumeDialogController.class);
- mController = mDependency.injectMockDependency(BluetoothController.class);
- when(mWifiManager.isWifiEnabled()).thenReturn(true);
-
- getContext().addMockSystemService(WifiManager.class, mWifiManager);
- getContext().addMockSystemService(TelecomManager.class, mTelecomManager);
-
- mDialog = new OutputChooserDialog(getContext(), mRouter);
- }
-
- @After
- public void tearDown() throws Exception {
- TestableLooper.get(this).processAllMessages();
- }
-/*
- @Test
- public void testClickMediaRouterItemConnectsMedia() {
- mDialog.show();
-
- OutputChooserLayout.Item item = new OutputChooserLayout.Item();
- item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
- MediaRouter.RouteInfo info = mock(MediaRouter.RouteInfo.class);
- when(info.isEnabled()).thenReturn(true);
- item.tag = info;
-
- mDialog.onDetailItemClick(item);
- verify(info, times(1)).select();
- verify(mController, never()).connect(any());
- mDialog.dismiss();
- }
-
- @Test
- public void testClickBtItemConnectsBt() {
- mDialog.show();
-
- OutputChooserLayout.Item item = new OutputChooserLayout.Item();
- item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
- CachedBluetoothDevice btDevice = mock(CachedBluetoothDevice.class);
- when(btDevice.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_DISCONNECTED);
- item.tag = btDevice;
-
- mDialog.onDetailItemClick(item);
- verify(mController, times(1)).connect(any());
- mDialog.dismiss();
- }
-
- @Test
- public void testTitleNotInCall() {
- mDialog.show();
-
- assertTrue(((TextView) mDialog.findViewById(R.id.title))
- .getText().toString().contains("Media"));
- mDialog.dismiss();
- }
-
- @Test
- public void testTitleInCall() {
- mDialog.setIsInCall(true);
- mDialog.show();
-
- assertTrue(((TextView) mDialog.findViewById(R.id.title))
- .getText().toString().contains("Phone"));
- mDialog.dismiss();
- }
-*/
- @Test
- public void testNoMediaScanIfInCall() {
- mDialog.setIsInCall(true);
- mDialog.onAttachedToWindow();
-
- verify(mRouter, never()).addCallback(any(), any(), anyInt());
- }
-
- @Test
- public void testRegisterCallbacks() {
- mDialog.setIsInCall(false);
- mDialog.onAttachedToWindow();
-
- if (INCLUDE_MEDIA_ROUTES) {
- verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
- }
- verify(mController, times(1)).addCallback(any());
- verify(mVolumeController, times(1)).addCallback(any(), any());
- }
-
- @Test
- public void testUnregisterCallbacks() {
- mDialog.setIsInCall(false);
- mDialog.onDetachedFromWindow();
-
- verify(mRouter, times(1)).removeCallback(any());
- verify(mController, times(1)).removeCallback(any());
- verify(mVolumeController, times(1)).removeCallback(any());
- }
-}
diff --git a/packages/VpnDialogs/res/values-as/strings.xml b/packages/VpnDialogs/res/values-as/strings.xml
new file mode 100644
index 0000000..4f16c74
--- /dev/null
+++ b/packages/VpnDialogs/res/values-as/strings.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2011 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="prompt" msgid="3183836924226407828">"সংযোগৰ অনুৰোধ"</string>
+ <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g>এ নেটৱৰ্ক ট্ৰেফিক নিৰীক্ষণ কৰিবলৈ এটা ভিপিএন সংযোগ ছেট আপ কৰিবলৈ বিচাৰিছে৷ আপুনি কেৱল উৎসটোক বিশ্বাস কৰিলেহে অনুৰোধ স্বীকাৰ কৰিব৷ ভিপিএন সক্ৰিয় থকাৰ সময়ত আপোনাৰ স্ক্ৰীণৰ ওপৰত <br /> <br /> <img src=vpn_icon /> দৃশ্যমান হয়৷"</string>
+ <string name="legacy_title" msgid="192936250066580964">"ভিপিএন সংযোগ হৈ আছে"</string>
+ <string name="session" msgid="6470628549473641030">"ছেশ্বন:"</string>
+ <string name="duration" msgid="3584782459928719435">"সময়সীমা:"</string>
+ <string name="data_transmitted" msgid="7988167672982199061">"পঠিওৱা হ\'ল:"</string>
+ <string name="data_received" msgid="4062776929376067820">"পোৱা গ\'ল:"</string>
+ <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_1">%2$s</xliff:g> পেকেট / <xliff:g id="NUMBER_0">%1$s</xliff:g> বাইট"</string>
+ <!-- no translation found for always_on_disconnected_title (1906740176262776166) -->
+ <skip />
+ <!-- no translation found for always_on_disconnected_message (555634519845992917) -->
+ <skip />
+ <!-- no translation found for always_on_disconnected_message_lockdown (4232225539869452120) -->
+ <skip />
+ <!-- no translation found for always_on_disconnected_message_separator (3310614409322581371) -->
+ <skip />
+ <!-- no translation found for always_on_disconnected_message_settings_link (6172280302829992412) -->
+ <skip />
+ <string name="configure" msgid="4905518375574791375">"কনফিগাৰ কৰক"</string>
+ <string name="disconnect" msgid="971412338304200056">"সংযোগ বিচ্ছিন্ন কৰক"</string>
+ <!-- no translation found for open_app (3717639178595958667) -->
+ <skip />
+ <!-- no translation found for dismiss (6192859333764711227) -->
+ <skip />
+</resources>
diff --git a/packages/VpnDialogs/res/values-or/strings.xml b/packages/VpnDialogs/res/values-or/strings.xml
new file mode 100644
index 0000000..3b4eedd
--- /dev/null
+++ b/packages/VpnDialogs/res/values-or/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2011 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">
+ <!-- no translation found for prompt (3183836924226407828) -->
+ <skip />
+ <!-- no translation found for warning (809658604548412033) -->
+ <skip />
+ <string name="legacy_title" msgid="192936250066580964">"VPN ସଂଯୋଗ ହେଲା"</string>
+ <string name="session" msgid="6470628549473641030">"ସେସନ୍:"</string>
+ <string name="duration" msgid="3584782459928719435">"ଅବଧି:"</string>
+ <string name="data_transmitted" msgid="7988167672982199061">"ପଠାଯାଇଛି:"</string>
+ <string name="data_received" msgid="4062776929376067820">"ପ୍ରାପ୍ତ ହୋଇଛି:"</string>
+ <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> ବାଇଟ୍ସ <xliff:g id="NUMBER_1">%2$s</xliff:g> ପ୍ୟାକେଟ୍ସ"</string>
+ <!-- no translation found for always_on_disconnected_title (1906740176262776166) -->
+ <skip />
+ <!-- no translation found for always_on_disconnected_message (555634519845992917) -->
+ <skip />
+ <!-- no translation found for always_on_disconnected_message_lockdown (4232225539869452120) -->
+ <skip />
+ <!-- no translation found for always_on_disconnected_message_separator (3310614409322581371) -->
+ <skip />
+ <!-- no translation found for always_on_disconnected_message_settings_link (6172280302829992412) -->
+ <skip />
+ <string name="configure" msgid="4905518375574791375">"କନଫିଗର୍ କରନ୍ତୁ"</string>
+ <string name="disconnect" msgid="971412338304200056">"ବିଚ୍ଛିନ୍ନ କରନ୍ତୁ"</string>
+ <!-- no translation found for open_app (3717639178595958667) -->
+ <skip />
+ <!-- no translation found for dismiss (6192859333764711227) -->
+ <skip />
+</resources>
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/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 5e5eacb..8b5176e 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;
@@ -55,6 +56,7 @@
import com.android.internal.telephony.PhoneConstantConversions;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.am.BatteryStatsService;
@@ -96,7 +98,8 @@
IPhoneStateListener callback;
IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
- int callerUserId;
+ int callerUid;
+ int callerPid;
int events;
@@ -120,7 +123,7 @@
+ " callback=" + callback
+ " onSubscriptionsChangedListenererCallback="
+ onSubscriptionsChangedListenerCallback
- + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId
+ + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId
+ " events=" + Integer.toHexString(events)
+ " canReadPhoneState=" + canReadPhoneState + "}";
}
@@ -374,26 +377,17 @@
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
+ " callback.asBinder=" + callback.asBinder());
}
- try {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
- "addOnSubscriptionsChangedListener");
- // SKIP checking for run-time permission since caller or self has PRIVILEGED permission
- } catch (SecurityException e) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PHONE_STATE,
- "addOnSubscriptionsChangedListener");
-
- if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return;
- }
+ if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
+ mContext, callingPackage, "addOnSubscriptionsChangedListener")) {
+ return;
}
@@ -408,7 +402,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 +474,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="
@@ -486,23 +483,14 @@
}
if (events != PhoneStateListener.LISTEN_NONE) {
- /* Checks permission and throws Security exception */
- checkListenerPermission(events);
-
- if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
- try {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
- // SKIP checking for run-time permission since caller or self has PRIVILEGED
- // permission
- } catch (SecurityException e) {
- if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return;
- }
- }
+ // Checks permission and throws SecurityException for disallowed operations. For pre-M
+ // apps whose runtime permission has been revoked, we return immediately to skip sending
+ // events to the app without crashing it.
+ if (!checkListenerPermission(events, callingPackage, "listen")) {
+ return;
}
+ int phoneId = SubscriptionManager.getPhoneId(subId);
synchronized (mRecords) {
// register
IBinder b = callback.asBinder();
@@ -514,10 +502,12 @@
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);
+ r.canReadPhoneState =
+ isPhoneStateEvent && canReadPhoneState(callingPackage, "listen");
// Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
// force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -525,9 +515,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 +560,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 +609,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);
}
@@ -675,21 +667,13 @@
}
}
- private boolean canReadPhoneState(String callingPackage) {
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) ==
- PackageManager.PERMISSION_GRANTED) {
- // SKIP checking for run-time permission since caller or self has PRIVILEGED permission
- return true;
- }
- boolean canReadPhoneState = mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED;
- if (canReadPhoneState &&
- mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ private boolean canReadPhoneState(String callingPackage, String message) {
+ try {
+ return TelephonyPermissions.checkCallingOrSelfReadPhoneState(
+ mContext, callingPackage, message);
+ } catch (SecurityException e) {
return false;
}
- return canReadPhoneState;
}
private String getCallIncomingNumber(Record record, int phoneId) {
@@ -1013,14 +997,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 +1087,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 +1115,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 +1157,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 +1281,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
@@ -1660,11 +1645,12 @@
}
private void enforceNotifyPermissionOrCarrierPrivilege(String method) {
- if (checkNotifyPermission()) {
+ if (checkNotifyPermission()) {
return;
}
- enforceCarrierPrivilege();
+ TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(
+ SubscriptionManager.getDefaultSubscriptionId(), method);
}
private boolean checkNotifyPermission(String method) {
@@ -1682,23 +1668,7 @@
== PackageManager.PERMISSION_GRANTED;
}
- private void enforceCarrierPrivilege() {
- TelephonyManager tm = TelephonyManager.getDefault();
- String[] pkgs = mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
- for (String pkg : pkgs) {
- if (tm.checkCarrierPrivilegesForPackage(pkg) ==
- TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
- return;
- }
- }
-
- String msg = "Carrier Privilege Permission Denial: from pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid();
- if (DBG) log(msg);
- throw new SecurityException(msg);
- }
-
- private void checkListenerPermission(int events) {
+ private boolean checkListenerPermission(int events, String callingPackage, String message) {
if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
@@ -1712,22 +1682,18 @@
}
if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
- try {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
- // SKIP checking for run-time permission since caller or self has PRIVILEGED
- // permission
- } catch (SecurityException e) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PHONE_STATE, null);
+ if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
+ mContext, callingPackage, message)) {
+ return false;
}
}
if ((events & PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
-
}
+
+ return true;
}
private void handleRemoveListLocked() {
@@ -1747,10 +1713,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 +1749,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 +1806,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 +1856,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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e8f0113..633c165 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5451,24 +5451,6 @@
}
}
- @Override
- public final void requestActivityRelaunch(IBinder token) {
- synchronized(this) {
- ActivityRecord r = ActivityRecord.isInStackLocked(token);
- if (r == null) {
- return;
- }
- final long origId = Binder.clearCallingIdentity();
- try {
- r.forceNewConfig = true;
- r.ensureActivityConfigurationLocked(0 /* globalChanges */,
- true /* preserveWindow */);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- }
-
/**
* This is the internal entry point for handling Activity.finish().
*
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/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/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 52f5917..f3f4dfd 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -43,8 +43,8 @@
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.net.Uri;
@@ -65,6 +65,7 @@
import android.provider.Settings;
import android.text.format.DateUtils;
import android.util.KeyValueListParser;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -79,9 +80,9 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.AppStateTracker;
import com.android.server.DeviceIdleController;
import com.android.server.FgThread;
-import com.android.server.AppStateTracker;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
@@ -125,8 +126,8 @@
*/
public final class JobSchedulerService extends com.android.server.SystemService
implements StateChangedListener, JobCompletedListener {
- static final String TAG = "JobSchedulerService";
- public static final boolean DEBUG = false;
+ public static final String TAG = "JobScheduler";
+ public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final boolean DEBUG_STANDBY = DEBUG || false;
/** The maximum number of concurrent jobs we run at one time. */
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 8d11d1e..021eccd 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -19,6 +19,7 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.os.UserHandle;
+import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -36,9 +37,9 @@
* out of idle state, it will be allowed to run scheduled jobs.
*/
public final class AppIdleController extends StateController {
-
- private static final String LOG_TAG = "AppIdleController";
- private static final boolean DEBUG = false;
+ private static final String TAG = "JobScheduler.AppIdle";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
// Singleton factory
private static Object sCreationLock = new Object();
@@ -56,7 +57,7 @@
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
jobStatus.getSourceUid(), jobStatus.getSourceUserId());
if (DEBUG) {
- Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
+ Slog.d(TAG, "Setting idle state of " + packageName + " to " + appIdle);
}
if (jobStatus.setAppNotIdleConstraintSatisfied(!appIdle)) {
mChanged = true;
@@ -81,7 +82,7 @@
&& jobStatus.getSourceUserId() == mUserId) {
if (jobStatus.setAppNotIdleConstraintSatisfied(!mIdle)) {
if (DEBUG) {
- Slog.d(LOG_TAG, "App Idle state changed, setting idle state of "
+ Slog.d(TAG, "App Idle state changed, setting idle state of "
+ mPackage + " to " + mIdle);
}
mChanged = true;
@@ -118,7 +119,7 @@
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
jobStatus.getSourceUid(), jobStatus.getSourceUserId());
if (DEBUG) {
- Slog.d(LOG_TAG, "Start tracking, setting idle state of "
+ Slog.d(TAG, "Start tracking, setting idle state of "
+ packageName + " to " + appIdle);
}
jobStatus.setAppNotIdleConstraintSatisfied(!appIdle);
@@ -229,7 +230,7 @@
@Override
public void onParoleStateChanged(boolean isParoleOn) {
if (DEBUG) {
- Slog.d(LOG_TAG, "Parole on: " + isParoleOn);
+ Slog.d(TAG, "Parole on: " + isParoleOn);
}
setAppIdleParoleOn(isParoleOn);
}
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index e8057fb..46ec5e5 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -34,9 +35,9 @@
import java.io.PrintWriter;
public final class BackgroundJobsController extends StateController {
-
- private static final String LOG_TAG = "BackgroundJobsController";
- private static final boolean DEBUG = JobSchedulerService.DEBUG;
+ private static final String TAG = "JobScheduler.Background";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
// Singleton factory
private static final Object sCreationLock = new Object();
@@ -179,7 +180,7 @@
final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0;
if (DEBUG) {
- Slog.d(LOG_TAG, String.format(
+ Slog.d(TAG, String.format(
"Job status updated: %d/%d checked/total jobs, %d us",
updateTrackedJobs.mCheckedCount,
updateTrackedJobs.mTotalCount,
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 8d3d116..263d99b 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -26,6 +26,7 @@
import android.os.BatteryManagerInternal;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -43,7 +44,9 @@
* ACTION_BATTERY_OK.
*/
public final class BatteryController extends StateController {
- private static final String TAG = "JobScheduler.Batt";
+ private static final String TAG = "JobScheduler.Battery";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
private static final Object sCreationLock = new Object();
private static volatile BatteryController sController;
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 77e813e..d7ef124 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -35,6 +35,7 @@
import android.os.UserHandle;
import android.text.format.DateUtils;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -56,8 +57,9 @@
*/
public final class ConnectivityController extends StateController implements
ConnectivityManager.OnNetworkActiveListener {
- private static final String TAG = "JobScheduler.Conn";
- private static final boolean DEBUG = false;
+ private static final String TAG = "JobScheduler.Connectivity";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
private final ConnectivityManager mConnManager;
private final NetworkPolicyManager mNetPolicyManager;
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index 7394e23f..90edde9 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -23,11 +23,12 @@
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -43,8 +44,9 @@
* Controller for monitoring changes to content URIs through a ContentObserver.
*/
public final class ContentObserverController extends StateController {
- private static final String TAG = "JobScheduler.Content";
- private static final boolean DEBUG = false;
+ private static final String TAG = "JobScheduler.ContentObserver";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
/**
* Maximum number of changing URIs we will batch together to report.
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 0dbcbee..323a126 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -27,6 +27,7 @@
import android.os.PowerManager;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
@@ -47,9 +48,10 @@
* When device is not dozing, set constraint for all jobs as satisfied.
*/
public final class DeviceIdleJobsController extends StateController {
+ private static final String TAG = "JobScheduler.DeviceIdle";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
- private static final String LOG_TAG = "DeviceIdleJobsController";
- private static final boolean LOG_DEBUG = false;
private static final long BACKGROUND_JOBS_DELAY = 3000;
static final int PROCESS_BACKGROUND_JOBS = 1;
@@ -104,8 +106,8 @@
synchronized (mLock) {
mDeviceIdleWhitelistAppIds =
mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
- if (LOG_DEBUG) {
- Slog.d(LOG_TAG, "Got whitelist "
+ if (DEBUG) {
+ Slog.d(TAG, "Got whitelist "
+ Arrays.toString(mDeviceIdleWhitelistAppIds));
}
}
@@ -114,8 +116,8 @@
synchronized (mLock) {
mPowerSaveTempWhitelistAppIds =
mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
- if (LOG_DEBUG) {
- Slog.d(LOG_TAG, "Got temp whitelist "
+ if (DEBUG) {
+ Slog.d(TAG, "Got temp whitelist "
+ Arrays.toString(mPowerSaveTempWhitelistAppIds));
}
boolean changed = false;
@@ -163,7 +165,7 @@
changed = true;
}
mDeviceIdleMode = enabled;
- if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
+ if (DEBUG) Slog.d(TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
if (enabled) {
mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
@@ -193,8 +195,8 @@
if (!changed) {
return;
}
- if (LOG_DEBUG) {
- Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
+ if (DEBUG) {
+ Slog.d(TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
}
mForegroundUids.put(uid, active);
mDeviceIdleUpdateFunctor.mChanged = false;
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index a9bc7e0..78284e5 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -26,6 +26,7 @@
import android.content.IntentFilter;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -37,7 +38,9 @@
import java.io.PrintWriter;
public final class IdleController extends StateController {
- private static final String TAG = "IdleController";
+ private static final String TAG = "JobScheduler.Idle";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
// Policy: we decide that we're "idle" if the device has been unused /
// screen off or dreaming for at least this long
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 3867306..d1bb63a 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -302,7 +302,6 @@
this.numFailures = numFailures;
int requiredConstraints = job.getConstraintFlags();
-
if (job.getRequiredNetwork() != null) {
requiredConstraints |= CONSTRAINT_CONNECTIVITY;
}
@@ -323,6 +322,13 @@
mInternalFlags = internalFlags;
updateEstimatedNetworkBytesLocked();
+
+ if (job.getRequiredNetwork() != null) {
+ // Later, when we check if a given network satisfies the required
+ // network, we need to know the UID that is requesting it, so push
+ // our source UID into place.
+ job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
+ }
}
/** Copy constructor: used specifically when cloning JobStatus objects for persistence,
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index d3055e6..88d6bea 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -30,7 +30,6 @@
* are ready to run, or whether they must be stopped.
*/
public abstract class StateController {
- protected static final boolean DEBUG = JobSchedulerService.DEBUG;
protected final Context mContext;
protected final Object mLock;
protected final StateChangedListener mStateChangedListener;
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
index 0519b63..5b79f39 100644
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -24,6 +24,7 @@
import android.content.IntentFilter;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -39,7 +40,9 @@
* Simple controller that tracks the status of the device's storage.
*/
public final class StorageController extends StateController {
- private static final String TAG = "JobScheduler.Stor";
+ private static final String TAG = "JobScheduler.Storage";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
private static final Object sCreationLock = new Object();
private static volatile StorageController sController;
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index a91f5a4..cdafc3b 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -25,6 +25,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -45,6 +46,8 @@
*/
public final class TimeController extends StateController {
private static final String TAG = "JobScheduler.Time";
+ private static final boolean DEBUG = JobSchedulerService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
/** Deadline alarm tag for logging purposes */
private final String DEADLINE_TAG = "*job.deadline*";
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/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 6deff36..838aa70 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2001,9 +2001,8 @@
}
@Override
- public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases,
- int status) throws RemoteException {
- mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status);
+ public void setRecoveryStatus(String alias, int status) throws RemoteException {
+ mRecoverableKeyStoreManager.setRecoveryStatus(alias, status);
}
public Map getRecoveryStatus(@Nullable String packageName) throws RemoteException {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 5460756..35954a5 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -277,29 +277,11 @@
}
/**
- * Updates recovery status for the application given its {@code packageName}.
- *
- * @param packageName which recoverable key statuses will be returned
- * @param aliases - KeyStore aliases or {@code null} for all aliases of the app
- * @param status - new status
+ * Sets the recovery status of key with {@code alias} to {@code status}.
*/
- public void setRecoveryStatus(
- @NonNull String packageName, @Nullable String[] aliases, int status)
- throws RemoteException {
+ public void setRecoveryStatus(String alias, int status) throws RemoteException {
checkRecoverKeyStorePermission();
- int uid = Binder.getCallingUid();
- if (packageName != null) {
- // TODO: get uid for package name, when many apps are supported.
- }
- if (aliases == null) {
- // Get all keys for the app.
- Map<String, Integer> allKeys = mDatabase.getStatusForAllKeys(uid);
- aliases = new String[allKeys.size()];
- allKeys.keySet().toArray(aliases);
- }
- for (String alias: aliases) {
- mDatabase.setRecoveryStatus(uid, alias, status);
- }
+ mDatabase.setRecoveryStatus(Binder.getCallingUid(), alias, status);
}
/**
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/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index e4c74ed..c11c099 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -490,7 +490,7 @@
* <p>This takes care of the resetting the counter for foreground apps as well as after
* locale changes.
*/
- public int getApiCallCount() {
+ public int getApiCallCount(boolean unlimited) {
final ShortcutService s = mShortcutUser.mService;
// Reset the counter if:
@@ -498,8 +498,9 @@
// - the package is *not* in foreground now, but was in foreground at some point
// since the previous time it had been.
if (s.isUidForegroundLocked(mPackageUid)
- || mLastKnownForegroundElapsedTime
- < s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) {
+ || (mLastKnownForegroundElapsedTime
+ < s.getUidLastForegroundElapsedTimeLocked(mPackageUid))
+ || unlimited) {
mLastKnownForegroundElapsedTime = s.injectElapsedRealtime();
resetRateLimiting();
}
@@ -538,10 +539,10 @@
* <p>This takes care of the resetting the counter for foreground apps as well as after
* locale changes, which is done internally by {@link #getApiCallCount}.
*/
- public boolean tryApiCall() {
+ public boolean tryApiCall(boolean unlimited) {
final ShortcutService s = mShortcutUser.mService;
- if (getApiCallCount() >= s.mMaxUpdatesPerInterval) {
+ if (getApiCallCount(unlimited) >= s.mMaxUpdatesPerInterval) {
return false;
}
mApiCallCount++;
@@ -1248,7 +1249,7 @@
pw.print(prefix);
pw.print(" ");
pw.print("Calls: ");
- pw.print(getApiCallCount());
+ pw.print(getApiCallCount(/*unlimited=*/ false));
pw.println();
// getApiCallCount() may have updated mLastKnownForegroundElapsedTime.
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 034fd23..076f81f 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -15,6 +15,7 @@
*/
package com.android.server.pm;
+import android.Manifest.permission;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1726,6 +1727,9 @@
final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
final int size = newShortcuts.size();
+ final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
+ injectBinderCallingPid(), injectBinderCallingUid());
+
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -1738,7 +1742,7 @@
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
// Throttling.
- if (!ps.tryApiCall()) {
+ if (!ps.tryApiCall(unlimited)) {
return false;
}
@@ -1777,6 +1781,9 @@
final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
final int size = newShortcuts.size();
+ final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
+ injectBinderCallingPid(), injectBinderCallingUid());
+
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -1790,7 +1797,7 @@
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE);
// Throttling.
- if (!ps.tryApiCall()) {
+ if (!ps.tryApiCall(unlimited)) {
return false;
}
@@ -1859,6 +1866,9 @@
final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
final int size = newShortcuts.size();
+ final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
+ injectBinderCallingPid(), injectBinderCallingUid());
+
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -1875,7 +1885,7 @@
assignImplicitRanks(newShortcuts);
// Throttling.
- if (!ps.tryApiCall()) {
+ if (!ps.tryApiCall(unlimited)) {
return false;
}
for (int i = 0; i < size; i++) {
@@ -2144,11 +2154,14 @@
public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
verifyCaller(packageName, userId);
+ final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
+ injectBinderCallingPid(), injectBinderCallingUid());
+
synchronized (mLock) {
throwIfUserLockedL(userId);
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- return mMaxUpdatesPerInterval - ps.getApiCallCount();
+ return mMaxUpdatesPerInterval - ps.getApiCallCount(unlimited);
}
}
@@ -2298,6 +2311,15 @@
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
}
+ /**
+ * Returns true if the caller has the "UNLIMITED_SHORTCUTS_API_CALLS" permission.
+ */
+ @VisibleForTesting
+ boolean injectHasUnlimitedShortcutsApiCallsPermission(int callingPid, int callingUid) {
+ return mContext.checkPermission(permission.UNLIMITED_SHORTCUTS_API_CALLS,
+ callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+ }
+
// This method is extracted so we can directly call this method from unit tests,
// even when hasShortcutPermission() is overridden.
@VisibleForTesting
@@ -4197,6 +4219,11 @@
return getCallingUid();
}
+ @VisibleForTesting
+ int injectBinderCallingPid() {
+ return getCallingPid();
+ }
+
private int getCallingUserId() {
return UserHandle.getUserId(injectBinderCallingUid());
}
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 872d723..23c4a33 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -92,6 +92,9 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
static final String REQUIRED_UPDATER_PERMISSION =
android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static final String REQUIRED_QUERY_PERMISSION =
+ android.Manifest.permission.QUERY_TIME_ZONE_RULES;
private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
@@ -131,7 +134,7 @@
@Override // Binder call
public RulesState getRulesState() {
- mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+ mPermissionHelper.enforceCallerHasPermission(REQUIRED_QUERY_PERMISSION);
return getRulesStateInternal();
}
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/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 0d36145..f0ca2ef 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -29,6 +29,7 @@
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
@@ -2138,6 +2139,10 @@
&& isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)) {
// Opening a new activity always supersedes a close for the anim.
setAppTransition(transit, flags);
+ } else if (isTaskTransit(transit) && isActivityTransit(mNextAppTransition)) {
+ // Task animations always supersede activity animations, because if we have both, it
+ // usually means that activity transition were just trampoline activities.
+ setAppTransition(transit, flags);
}
}
boolean prepared = prepare();
@@ -2162,6 +2167,21 @@
|| transit == TRANSIT_KEYGUARD_UNOCCLUDE;
}
+ private static boolean isTaskTransit(int transit) {
+ return transit == TRANSIT_TASK_OPEN
+ || transit == TRANSIT_TASK_CLOSE
+ || transit == TRANSIT_TASK_OPEN_BEHIND
+ || transit == TRANSIT_TASK_TO_BACK
+ || transit == TRANSIT_TASK_TO_FRONT
+ || transit == TRANSIT_TASK_IN_PLACE;
+ }
+
+ private static boolean isActivityTransit(int transit) {
+ return transit == TRANSIT_ACTIVITY_OPEN
+ || transit == TRANSIT_ACTIVITY_CLOSE
+ || transit == TRANSIT_ACTIVITY_RELAUNCH;
+ }
+
/**
* @return whether the transition should show the thumbnail being scaled down.
*/
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index d646c07..42d6ec0 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1668,6 +1668,9 @@
}
if (adapter != null) {
startAnimation(getPendingTransaction(), adapter, !isVisible());
+ if (adapter.getShowWallpaper()) {
+ mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ }
}
} else {
cancelAnimation();
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/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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 61c8b79..d24b3ee 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1270,7 +1270,8 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
|| mPackageManager.hasSystemFeature(
- PackageManager.FEATURE_USB_ACCESSORY)) {
+ PackageManager.FEATURE_USB_ACCESSORY)
+ || isEmulator) {
// Manage USB host and device support
traceBeginAndSlog("StartUsbService");
mSystemServiceManager.startService(USB_SERVICE_CLASS);
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/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 8bd0df4..3c35c5b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -639,7 +639,7 @@
}
@Test
- public void setRecoveryStatus_forOneAlias() throws Exception {
+ public void setRecoveryStatus() throws Exception {
int userId = UserHandle.getCallingUserId();
int uid = Binder.getCallingUid();
int status = 100;
@@ -652,55 +652,12 @@
assertThat(statuses).hasSize(1);
assertThat(statuses).containsEntry(alias, status);
- mRecoverableKeyStoreManager.setRecoveryStatus(
- /*packageName=*/ null, new String[] {alias}, status2);
+ mRecoverableKeyStoreManager.setRecoveryStatus(alias, status2);
statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
assertThat(statuses).hasSize(1);
assertThat(statuses).containsEntry(alias, status2); // updated
}
- @Test
- public void setRecoveryStatus_for2Aliases() throws Exception {
- int userId = UserHandle.getCallingUserId();
- int uid = Binder.getCallingUid();
- int status = 100;
- int status2 = 200;
- int status3 = 300;
- String alias = "key1";
- String alias2 = "key2";
- WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status);
- mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey);
- mRecoverableKeyStoreDb.insertKey(userId, uid, alias2, wrappedKey);
- Map<String, Integer> statuses =
- mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
- assertThat(statuses).hasSize(2);
- assertThat(statuses).containsEntry(alias, status);
- assertThat(statuses).containsEntry(alias2, status);
-
- mRecoverableKeyStoreManager.setRecoveryStatus(
- /*packageName=*/ null, /*aliases=*/ null, status2);
- statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
- assertThat(statuses).hasSize(2);
- assertThat(statuses).containsEntry(alias, status2); // updated
- assertThat(statuses).containsEntry(alias2, status2); // updated
-
- mRecoverableKeyStoreManager.setRecoveryStatus(
- /*packageName=*/ null, new String[] {alias2}, status3);
-
- statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
- assertThat(statuses).hasSize(2);
- assertThat(statuses).containsEntry(alias, status2);
- assertThat(statuses).containsEntry(alias2, status3); // updated
-
- mRecoverableKeyStoreManager.setRecoveryStatus(
- /*packageName=*/ null, new String[] {alias, alias2}, status);
-
- statuses = mRecoverableKeyStoreManager.getRecoveryStatus(/*packageName=*/ null);
- assertThat(statuses).hasSize(2);
- assertThat(statuses).containsEntry(alias, status); // updated
- assertThat(statuses).containsEntry(alias2, status); // updated
- }
-
private static byte[] encryptedApplicationKey(
SecretKey recoveryKey, byte[] applicationKey) throws Exception {
return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 8cf575e..4ca1647 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -289,6 +289,12 @@
}
@Override
+ int injectBinderCallingPid() {
+ // Note it's not used in tests, so just return a "random" value.
+ return mInjectedCallingUid * 123;
+ }
+
+ @Override
int injectGetPackageUid(String packageName, int userId) {
return getInjectedPackageInfo(packageName, userId, false).applicationInfo.uid;
}
@@ -325,6 +331,11 @@
}
@Override
+ boolean injectHasUnlimitedShortcutsApiCallsPermission(int callingPid, int callingUid) {
+ return mInjectHasUnlimitedShortcutsApiCallsPermission;
+ }
+
+ @Override
ComponentName getDefaultLauncher(@UserIdInt int userId) {
final ComponentName activity = mDefaultLauncher.get(userId);
if (activity != null) {
@@ -519,6 +530,12 @@
}
@Override
+ int injectBinderCallingPid() {
+ // Note it's not used in tests, so just return a "random" value.
+ return mInjectedCallingUid * 123;
+ }
+
+ @Override
long injectClearCallingIdentity() {
final int prevCallingUid = mInjectedCallingUid;
mInjectedCallingUid = Process.SYSTEM_UID;
@@ -705,6 +722,8 @@
protected boolean mInjectCheckAccessShortcutsPermission = false;
+ protected boolean mInjectHasUnlimitedShortcutsApiCallsPermission = false;
+
static {
QUERY_ALL.setQueryFlags(
ShortcutQuery.FLAG_GET_ALL_KINDS);
@@ -1207,7 +1226,7 @@
}
/**
- * This controls {@link ShortcutService#hasShortcutHostPermission(String, int)}, but
+ * This controls {@link ShortcutService#hasShortcutHostPermission}, but
* not {@link ShortcutService#getDefaultLauncher(int)}. To control the later, use
* {@link #setDefaultLauncher(int, ComponentName)}.
*/
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 857925b..845e05d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -366,7 +366,38 @@
});
}
- public void testPublishWithNoActivity() {
+ public void testUnlimitedCalls() {
+ setCaller(CALLING_PACKAGE_1, USER_0);
+
+ final ShortcutInfo si1 = makeShortcut("shortcut1");
+
+ assertEquals(3, mManager.getRemainingCallCount());
+
+ assertTrue(mManager.setDynamicShortcuts(list(si1)));
+ assertEquals(2, mManager.getRemainingCallCount());
+
+ assertTrue(mManager.addDynamicShortcuts(list(si1)));
+ assertEquals(1, mManager.getRemainingCallCount());
+
+ assertTrue(mManager.updateShortcuts(list(si1)));
+ assertEquals(0, mManager.getRemainingCallCount());
+
+ // Unlimited now.
+ mInjectHasUnlimitedShortcutsApiCallsPermission = true;
+
+ assertEquals(3, mManager.getRemainingCallCount());
+
+ assertTrue(mManager.setDynamicShortcuts(list(si1)));
+ assertEquals(3, mManager.getRemainingCallCount());
+
+ assertTrue(mManager.addDynamicShortcuts(list(si1)));
+ assertEquals(3, mManager.getRemainingCallCount());
+
+ assertTrue(mManager.updateShortcuts(list(si1)));
+ assertEquals(3, mManager.getRemainingCallCount());
+ }
+
+ public void testPublishWithNoActivity() {
// If activity is not explicitly set, use the default one.
mRunningUsers.put(USER_10, true);
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
index f5969f3..c252609 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -41,6 +41,7 @@
import libcore.io.IoUtils;
+import static com.android.server.timezone.RulesManagerService.REQUIRED_QUERY_PERMISSION;
import static com.android.server.timezone.RulesManagerService.REQUIRED_UPDATER_PERMISSION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -91,25 +92,25 @@
@Test(expected = SecurityException.class)
public void getRulesState_noCallerPermission() throws Exception {
- configureCallerDoesNotHavePermission();
+ configureCallerDoesNotHaveQueryPermission();
mRulesManagerService.getRulesState();
}
@Test(expected = SecurityException.class)
public void requestInstall_noCallerPermission() throws Exception {
- configureCallerDoesNotHavePermission();
+ configureCallerDoesNotHaveUpdatePermission();
mRulesManagerService.requestInstall(null, null, null);
}
@Test(expected = SecurityException.class)
public void requestUninstall_noCallerPermission() throws Exception {
- configureCallerDoesNotHavePermission();
+ configureCallerDoesNotHaveUpdatePermission();
mRulesManagerService.requestUninstall(null, null);
}
@Test(expected = SecurityException.class)
public void requestNothing_noCallerPermission() throws Exception {
- configureCallerDoesNotHavePermission();
+ configureCallerDoesNotHaveUpdatePermission();
mRulesManagerService.requestNothing(null, true);
}
@@ -916,12 +917,18 @@
.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
}
- private void configureCallerDoesNotHavePermission() {
+ private void configureCallerDoesNotHaveUpdatePermission() {
doThrow(new SecurityException("Simulated permission failure"))
.when(mMockPermissionHelper)
.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
}
+ private void configureCallerDoesNotHaveQueryPermission() {
+ doThrow(new SecurityException("Simulated permission failure"))
+ .when(mMockPermissionHelper)
+ .enforceCallerHasPermission(REQUIRED_QUERY_PERMISSION);
+ }
+
private void configureStageInstallExpectation(int resultCode)
throws Exception {
when(mMockTimeZoneDistroInstaller.stageInstallWithErrorCode(any(TimeZoneDistro.class)))
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/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 02cc82c..9e2b519 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -952,6 +952,11 @@
int getCarrierPrivilegeStatus(int subId);
/**
+ * Similar to above, but check for the given uid.
+ */
+ int getCarrierPrivilegeStatusForUid(int subId, int uid);
+
+ /**
* Similar to above, but check for the package whose name is pkgName.
*/
int checkCarrierPrivilegesForPackage(String pkgName);
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
new file mode 100644
index 0000000..da8471f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -0,0 +1,209 @@
+/*
+ * 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.telephony;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.ITelephony;
+
+/** Utility class for Telephony permission enforcement. */
+public final class TelephonyPermissions {
+ private static final String LOG_TAG = "TelephonyPermissions";
+
+ private static final boolean DBG = false;
+
+ private TelephonyPermissions() {}
+
+ /**
+ * Check whether the caller (or self, if not processing an IPC) can read phone state.
+ *
+ * <p>This method behaves in one of the following ways:
+ * <ul>
+ * <li>return true: if the caller has either the READ_PRIVILEGED_PHONE_STATE permission or the
+ * READ_PHONE_STATE runtime permission.
+ * <li>throw SecurityException: if the caller didn't declare any of these permissions, or, for
+ * apps which support runtime permissions, if the caller does not currently have any of
+ * these permissions.
+ * <li>return false: if the caller lacks all of these permissions and doesn't support runtime
+ * permissions. This implies that the user revoked the ability to read phone state
+ * manually (via AppOps). In this case we can't throw as it would break app compatibility,
+ * so we return false to indicate that the calling function should return dummy data.
+ * </ul>
+ */
+ public static boolean checkCallingOrSelfReadPhoneState(
+ Context context, String callingPackage, String message) {
+ return checkReadPhoneState(context, Binder.getCallingPid(), Binder.getCallingUid(),
+ callingPackage, message);
+ }
+
+ /**
+ * Check whether the app with the given pid/uid can read phone state.
+ *
+ * <p>This method behaves in one of the following ways:
+ * <ul>
+ * <li>return true: if the caller has either the READ_PRIVILEGED_PHONE_STATE permission or the
+ * READ_PHONE_STATE runtime permission.
+ * <li>throw SecurityException: if the caller didn't declare any of these permissions, or, for
+ * apps which support runtime permissions, if the caller does not currently have any of
+ * these permissions.
+ * <li>return false: if the caller lacks all of these permissions and doesn't support runtime
+ * permissions. This implies that the user revoked the ability to read phone state
+ * manually (via AppOps). In this case we can't throw as it would break app compatibility,
+ * so we return false to indicate that the calling function should return dummy data.
+ * </ul>
+ */
+ public static boolean checkReadPhoneState(
+ Context context, int pid, int uid, String callingPackage, String message) {
+ try {
+ context.enforcePermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message);
+
+ // SKIP checking for run-time permission since caller has PRIVILEGED permission
+ return true;
+ } catch (SecurityException privilegedPhoneStateException) {
+ context.enforcePermission(
+ android.Manifest.permission.READ_PHONE_STATE, pid, uid, message);
+ }
+
+ // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been
+ // revoked.
+ AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ return appOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, uid, callingPackage) ==
+ AppOpsManager.MODE_ALLOWED;
+ }
+
+ /**
+ * Returns whether the caller can read phone numbers.
+ *
+ * <p>Besides apps with the ability to read phone state per {@link #checkReadPhoneState}, the
+ * default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS can also read phone numbers.
+ */
+ public static boolean checkCallingOrSelfReadPhoneNumber(
+ Context context, String callingPackage, String message) {
+ return checkReadPhoneNumber(
+ context, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, message);
+ }
+
+ @VisibleForTesting
+ public static boolean checkReadPhoneNumber(
+ Context context, int pid, int uid, String callingPackage, String message) {
+ // Default SMS app can always read it.
+ AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ if (appOps.noteOp(AppOpsManager.OP_WRITE_SMS, uid, callingPackage) ==
+ AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+
+ // NOTE(b/73308711): If an app has one of the following AppOps bits explicitly revoked, they
+ // will be denied access, even if they have another permission and AppOps bit if needed.
+
+ // First, check if we can read the phone state.
+ try {
+ return checkReadPhoneState(context, pid, uid, callingPackage, message);
+ } catch (SecurityException readPhoneStateSecurityException) {
+ }
+ // Can be read with READ_SMS too.
+ try {
+ context.enforcePermission(android.Manifest.permission.READ_SMS, pid, uid, message);
+ int opCode = AppOpsManager.permissionToOpCode(android.Manifest.permission.READ_SMS);
+ if (opCode != AppOpsManager.OP_NONE) {
+ return appOps.noteOp(opCode, uid, callingPackage) == AppOpsManager.MODE_ALLOWED;
+ } else {
+ return true;
+ }
+ } catch (SecurityException readSmsSecurityException) {
+ }
+ // Can be read with READ_PHONE_NUMBERS too.
+ try {
+ context.enforcePermission(android.Manifest.permission.READ_PHONE_NUMBERS, pid, uid,
+ message);
+ int opCode = AppOpsManager.permissionToOpCode(
+ android.Manifest.permission.READ_PHONE_NUMBERS);
+ if (opCode != AppOpsManager.OP_NONE) {
+ return appOps.noteOp(opCode, uid, callingPackage) == AppOpsManager.MODE_ALLOWED;
+ } else {
+ return true;
+ }
+ } catch (SecurityException readPhoneNumberSecurityException) {
+ }
+
+ throw new SecurityException(message + ": Neither user " + uid +
+ " nor current process has " + android.Manifest.permission.READ_PHONE_STATE +
+ ", " + android.Manifest.permission.READ_SMS + ", or " +
+ android.Manifest.permission.READ_PHONE_NUMBERS);
+ }
+
+ /**
+ * Ensure the caller (or self, if not processing an IPC) has MODIFY_PHONE_STATE (and is thus a
+ * privileged app) or carrier privileges.
+ *
+ * @throws SecurityException if the caller does not have the required permission/privileges
+ */
+ public static void enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+ Context context, int subId, String message) {
+ if (context.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) ==
+ PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next.");
+ enforceCallingOrSelfCarrierPrivilege(subId, message);
+ }
+
+ /**
+ * Make sure the caller (or self, if not processing an IPC) has carrier privileges.
+ *
+ * @throws SecurityException if the caller does not have the required privileges
+ */
+ public static void enforceCallingOrSelfCarrierPrivilege(int subId, String message) {
+ // NOTE: It's critical that we explicitly pass the calling UID here rather than call
+ // TelephonyManager#hasCarrierPrivileges directly, as the latter only works when called from
+ // the phone process. When called from another process, it will check whether that process
+ // has carrier privileges instead.
+ enforceCarrierPrivilege(subId, Binder.getCallingUid(), message);
+ }
+
+ private static void enforceCarrierPrivilege(int subId, int uid, String message) {
+ if (getCarrierPrivilegeStatus(subId, uid) !=
+ TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege.");
+ throw new SecurityException(message);
+ }
+ }
+
+ private static int getCarrierPrivilegeStatus(int subId, int uid) {
+ ITelephony telephony =
+ ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ try {
+ if (telephony != null) {
+ return telephony.getCarrierPrivilegeStatusForUid(subId, uid);
+ }
+ } catch (RemoteException e) {
+ // Fallback below.
+ }
+ Rlog.e(LOG_TAG, "Phone process is down, cannot check carrier privileges");
+ return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+ }
+}
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index a2edb04..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",
],
@@ -44,9 +43,8 @@
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/test-app/AndroidManifest.xml b/tests/ActivityManagerPerfTests/test-app/AndroidManifest.xml
index 23a151c..32905a9 100644
--- a/tests/ActivityManagerPerfTests/test-app/AndroidManifest.xml
+++ b/tests/ActivityManagerPerfTests/test-app/AndroidManifest.xml
@@ -19,7 +19,6 @@
android:minSdkVersion="21"
android:targetSdkVersion="27" />
<application android:name=".TestApplication">
- <activity android:name=".TestActivity" android:exported="true"/>
<provider
android:authorities="com.android.frameworks.perftests.amteststestapp"
android:name=".TestContentProvider"
@@ -33,6 +32,9 @@
</intent-filter>
</receiver>
<service
+ android:name=".StartProcessService"
+ android:exported="true" />
+ <service
android:name=".TestService"
android:exported="true" />
</application>
diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/StartProcessService.java
similarity index 60%
rename from tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
rename to tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/StartProcessService.java
index 4e7bb4c..054097e 100644
--- a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
+++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/StartProcessService.java
@@ -16,19 +16,26 @@
package com.android.frameworks.perftests.amteststestapp;
-import android.app.Activity;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.Looper;
+import android.util.Log;
-import com.android.frameworks.perftests.am.util.Constants;
import com.android.frameworks.perftests.am.util.Utils;
-public class TestActivity extends Activity {
+/**
+ * Service used to start up the target package and make sure it's running.
+ * Should be bound to, then wait for it to call the ILooperIdleCallback.
+ */
+public class StartProcessService extends Service {
@Override
- protected void onResume() {
- super.onResume();
- Looper.myQueue().addIdleHandler(() -> {
- Utils.sendTime(getIntent(), Constants.TYPE_TARGET_PACKAGE_START);
+ public IBinder onBind(Intent intent) {
+ Looper.getMainLooper().getQueue().addIdleHandler(() -> {
+ Utils.sendLooperIdle(intent);
return false;
});
+ return new Binder();
}
}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
index cf175e0..58fb136 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
@@ -16,11 +16,9 @@
package com.android.frameworks.perftests.am.tests;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.os.IBinder;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.PerfManualStatusReporter;
import android.support.test.InstrumentationRegistry;
@@ -28,20 +26,16 @@
import com.android.frameworks.perftests.am.util.TargetPackageUtils;
import com.android.frameworks.perftests.am.util.TimeReceiver;
-import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.function.LongSupplier;
public class BasePerfTest {
private static final String TAG = BasePerfTest.class.getSimpleName();
- private static final long AWAIT_SERVICE_CONNECT_MS = 2000;
private TimeReceiver mTimeReceiver;
+ private ServiceConnection mAliveServiceConnection;
@Rule
public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
@@ -54,11 +48,6 @@
mTimeReceiver = new TimeReceiver();
}
- @After
- public void tearDown() {
- TargetPackageUtils.killTargetPackage(mContext);
- }
-
protected void addReceivedTimeNs(String type) {
mTimeReceiver.addTimeForTypeToQueue(type, System.nanoTime());
}
@@ -71,46 +60,6 @@
return intent;
}
- protected ServiceConnection bindAndWaitForConnectedService() {
- return bindAndWaitForConnectedService(0);
- }
-
- protected ServiceConnection bindAndWaitForConnectedService(int flags) {
- CountDownLatch countDownLatch = new CountDownLatch(1);
- final ServiceConnection serviceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- countDownLatch.countDown();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- }
- };
-
- final Intent intent = createServiceIntent();
- final boolean success = mContext.bindService(intent, serviceConnection,
- Context.BIND_AUTO_CREATE | flags);
- Assert.assertTrue("Could not bind to service", success);
-
- try {
- boolean connectedSuccess = countDownLatch.await(AWAIT_SERVICE_CONNECT_MS,
- TimeUnit.MILLISECONDS);
- Assert.assertTrue("Timeout when waiting for ServiceConnection.onServiceConnected()",
- connectedSuccess);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
-
- return serviceConnection;
- }
-
- protected void unbindFromService(ServiceConnection serviceConnection) {
- if (serviceConnection != null) {
- mContext.unbindService(serviceConnection);
- }
- }
-
protected Intent createBroadcastIntent(String action) {
final Intent intent = new Intent(action);
intent.addFlags(
@@ -125,11 +74,14 @@
private void setUpIteration() {
mTimeReceiver.clear();
- TargetPackageUtils.killTargetPackage(mContext);
+ }
+
+ private void tearDownIteration() {
+ TargetPackageUtils.killTargetPackage(mContext, mAliveServiceConnection);
}
protected void startTargetPackage() {
- TargetPackageUtils.startTargetPackage(mContext, mTimeReceiver);
+ mAliveServiceConnection = TargetPackageUtils.startTargetPackage(mContext);
}
protected long getReceivedTimeNs(String type) {
@@ -142,6 +94,7 @@
while (benchmarkState.keepRunning(elapsedTimeNs)) {
setUpIteration();
elapsedTimeNs = func.getAsLong();
+ tearDownIteration();
}
}
}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/ServiceBindPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/ServiceBindPerfTest.java
index 6d2935a..e1263db 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/ServiceBindPerfTest.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/ServiceBindPerfTest.java
@@ -25,6 +25,7 @@
import android.support.test.runner.AndroidJUnit4;
import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.TargetPackageUtils;
import org.junit.Assert;
import org.junit.Test;
@@ -75,7 +76,7 @@
final long endTimeNs = getReceivedTimeNs(Constants.TYPE_SERVICE_CONNECTED);
return endTimeNs - startTimeNs;
} finally {
- unbindFromService(serviceConnection);
+ TargetPackageUtils.unbindFromService(mContext, serviceConnection);
}
});
}
@@ -97,7 +98,7 @@
final long endTimeNs = getReceivedTimeNs(Constants.TYPE_SERVICE_CONNECTED);
return endTimeNs - startTimeNs;
} finally {
- unbindFromService(serviceConnection);
+ TargetPackageUtils.unbindFromService(mContext, serviceConnection);
}
});
}
@@ -112,7 +113,8 @@
startTargetPackage();
final Intent intent = createServiceIntent();
- final ServiceConnection alreadyBoundServiceConnection = bindAndWaitForConnectedService();
+ final ServiceConnection alreadyBoundServiceConnection =
+ TargetPackageUtils.bindAndWaitForConnectedService(mContext, intent);
try {
final ServiceConnection serviceConnection = createServiceConnectionReportTime();
@@ -123,10 +125,10 @@
final long endTimeNs = getReceivedTimeNs(Constants.TYPE_SERVICE_CONNECTED);
return endTimeNs - startTimeNs;
} finally {
- unbindFromService(serviceConnection);
+ TargetPackageUtils.unbindFromService(mContext, serviceConnection);
}
} finally {
- unbindFromService(alreadyBoundServiceConnection);
+ TargetPackageUtils.unbindFromService(mContext, alreadyBoundServiceConnection);
}
});
}
@@ -139,8 +141,9 @@
public void bindServiceAllowOomManagement() {
runPerfFunction(() -> {
final Intent intentNoOom = createServiceIntent();
- final ServiceConnection serviceConnectionOom = bindAndWaitForConnectedService(
- Context.BIND_ALLOW_OOM_MANAGEMENT);
+ final ServiceConnection serviceConnectionOom =
+ TargetPackageUtils.bindAndWaitForConnectedService(mContext, intentNoOom,
+ Context.BIND_ALLOW_OOM_MANAGEMENT);
try {
final ServiceConnection serviceConnectionNoOom =
@@ -152,10 +155,10 @@
return endTimeNs - startTimeNs;
} finally {
- unbindFromService(serviceConnectionNoOom);
+ TargetPackageUtils.unbindFromService(mContext, serviceConnectionNoOom);
}
} finally {
- unbindFromService(serviceConnectionOom);
+ TargetPackageUtils.unbindFromService(mContext, serviceConnectionOom);
}
});
}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/ServiceStartPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/ServiceStartPerfTest.java
index 626ee02..f05f323 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/ServiceStartPerfTest.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/ServiceStartPerfTest.java
@@ -23,6 +23,7 @@
import android.support.test.runner.AndroidJUnit4;
import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.TargetPackageUtils;
import org.junit.Assert;
import org.junit.Test;
@@ -86,7 +87,8 @@
public void startServiceAlreadyBound() {
runPerfFunction(() -> {
final ServiceConnection alreadyBoundServiceConnection =
- bindAndWaitForConnectedService();
+ TargetPackageUtils.bindAndWaitForConnectedService(mContext,
+ createServiceIntent());
try {
final Intent intent = createServiceIntent();
@@ -96,20 +98,21 @@
return endTimeNs - startTimeNs;
} finally {
- unbindFromService(alreadyBoundServiceConnection);
+ TargetPackageUtils.unbindFromService(mContext, alreadyBoundServiceConnection);
}
});
}
/**
* Benchmark time from Context.startService() with FLAG_GRANT_READ_URI_PERMISSION to
- * Service.onStartCommand() when target process is running.
+ * Service.onStartCommand() when target service is already running.
*/
@Test
public void startServiceProcessRunningReadUriPermission() {
runPerfFunction(() -> {
final ServiceConnection alreadyBoundServiceConnection =
- bindAndWaitForConnectedService();
+ TargetPackageUtils.bindAndWaitForConnectedService(mContext,
+ createServiceIntent());
try {
final Intent intent = createServiceIntent();
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -120,7 +123,7 @@
return endTimeNs - startTimeNs;
} finally {
- unbindFromService(alreadyBoundServiceConnection);
+ TargetPackageUtils.unbindFromService(mContext, alreadyBoundServiceConnection);
}
});
}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
index 3db8abc..046dd6b 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
@@ -17,19 +17,33 @@
package com.android.frameworks.perftests.am.util;
import android.app.ActivityManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ResultReceiver;
import android.os.SystemClock;
+import org.junit.Assert;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
public class TargetPackageUtils {
private static final String TAG = TargetPackageUtils.class.getSimpleName();
public static final String PACKAGE_NAME = "com.android.frameworks.perftests.amteststestapp";
public static final String ACTIVITY_NAME = PACKAGE_NAME + ".TestActivity";
public static final String SERVICE_NAME = PACKAGE_NAME + ".TestService";
+ private static final String START_SERVICE_NAME = PACKAGE_NAME + ".StartProcessService";
private static final long WAIT_TIME_MS = 100L;
+ private static final long AWAIT_SERVICE_CONNECT_MS = 10000L;
// Cache for test app's uid, so we only have to query it once.
private static int sTestAppUid = -1;
@@ -37,11 +51,13 @@
/**
* Kills the test package synchronously.
*/
- public static void killTargetPackage(Context context) {
- ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+ public static void killTargetPackage(Context context, ServiceConnection serviceConnection) {
+ unbindFromService(context, serviceConnection);
+
+ final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
activityManager.forceStopPackage(PACKAGE_NAME);
while (targetPackageIsRunning(context)) {
- sleep();
+ SystemClock.sleep(WAIT_TIME_MS);
}
Utils.drainBroadcastQueue();
@@ -50,21 +66,24 @@
/**
* Starts the test package synchronously. It does so by starting an Activity.
*/
- public static void startTargetPackage(Context context, TimeReceiver timeReceiver) {
- // "am start-activity -W PACKAGE_NAME/ACTIVITY_CLASS_NAME" still requires a sleep even
- // though it should be synchronous, so just use Intent instead
+ public static ServiceConnection startTargetPackage(Context context) {
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
final Intent intent = new Intent();
- intent.putExtras(timeReceiver.createReceiveTimeExtraBinder());
- intent.setClassName(PACKAGE_NAME, ACTIVITY_NAME);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
+ intent.putExtra(Intent.EXTRA_RESULT_RECEIVER, new CountDownResultReceiver(countDownLatch));
+ intent.setClassName(PACKAGE_NAME, START_SERVICE_NAME);
+ final ServiceConnection serviceConnection = bindAndWaitForConnectedService(context, intent);
- while (!targetPackageIsRunning(context)) {
- sleep();
+ try {
+ final boolean targetPackageIdleSuccess = countDownLatch.await(AWAIT_SERVICE_CONNECT_MS,
+ TimeUnit.MILLISECONDS);
+ Assert.assertTrue("Timeout when waiting for ILooperIdleCallback.Stub.looperIdle()",
+ targetPackageIdleSuccess);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
}
- // make sure Application has run
- timeReceiver.getReceivedTimeNs(Constants.TYPE_TARGET_PACKAGE_START);
+
Utils.drainBroadcastQueue();
+ return serviceConnection;
}
private static boolean targetPackageIsRunning(Context context) {
@@ -74,10 +93,6 @@
return !result.contains("(NONEXISTENT)");
}
- private static void sleep() {
- SystemClock.sleep(WAIT_TIME_MS);
- }
-
private static int getTestAppUid(Context context) {
if (sTestAppUid == -1) {
final PackageManager pm = context.getPackageManager();
@@ -90,5 +105,45 @@
return sTestAppUid;
}
+ public static ServiceConnection bindAndWaitForConnectedService(Context context, Intent intent) {
+ return bindAndWaitForConnectedService(context, intent, 0);
+ }
+
+ public static ServiceConnection bindAndWaitForConnectedService(Context context, Intent intent,
+ int bindFlags) {
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ final ServiceConnection serviceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ countDownLatch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+
+ final boolean success = context.bindService(intent, serviceConnection,
+ Context.BIND_AUTO_CREATE | bindFlags);
+ Assert.assertTrue("Could not bind to service", success);
+
+ try {
+ final boolean connectedSuccess = countDownLatch.await(AWAIT_SERVICE_CONNECT_MS,
+ TimeUnit.MILLISECONDS);
+ Assert.assertTrue("Timeout when waiting for ServiceConnection.onServiceConnected()",
+ connectedSuccess);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ return serviceConnection;
+ }
+
+ public static void unbindFromService(Context context, ServiceConnection serviceConnection) {
+ if (serviceConnection != null) {
+ context.unbindService(serviceConnection);
+ }
+ }
+
}
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
index ffb3f84..9b076c5 100644
--- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
@@ -29,4 +29,5 @@
"com.android.frameworks.perftests.ACTION_BROADCAST_REGISTERED_RECEIVE";
public static final String EXTRA_RECEIVER_CALLBACK = "receiver_callback_binder";
+ public static final String EXTRA_LOOPER_IDLE_CALLBACK = "looper_idle_callback_binder";
}
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/CountDownResultReceiver.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/CountDownResultReceiver.java
new file mode 100644
index 0000000..6c032c4
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/CountDownResultReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * 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.frameworks.perftests.am.util;
+
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+import java.util.concurrent.CountDownLatch;
+
+public class CountDownResultReceiver extends ResultReceiver {
+ private CountDownLatch mCountDownLatch;
+
+ public CountDownResultReceiver(CountDownLatch countDownLatch) {
+ super(null);
+ mCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ mCountDownLatch.countDown();
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
index 493d8cd..67071d2 100644
--- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
@@ -18,6 +18,7 @@
import android.content.Intent;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;
@@ -56,4 +57,12 @@
Log.e(TAG, e.getMessage());
}
}
+
+ /**
+ * Notify the listener that the main Looper queue is idle.
+ */
+ public static void sendLooperIdle(Intent intent) {
+ ResultReceiver resultReceiver = intent.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER);
+ resultReceiver.send(0, null);
+ }
}
diff --git a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
index b3f2686..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";
@@ -790,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%),
@@ -799,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