Merge changes from topic "actor-signature-policy-rvc-dev" into rvc-dev
* changes:
Actor signature overlayable policy
Refactor overlayable policy
diff --git a/Android.bp b/Android.bp
index 4a1f96e..5e068ee 100644
--- a/Android.bp
+++ b/Android.bp
@@ -481,6 +481,7 @@
"framework-platform-compat-config",
"libcore-platform-compat-config",
"services-platform-compat-config",
+ "documents-ui-compat-config",
],
static_libs: [
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
diff --git a/apex/Android.bp b/apex/Android.bp
index 362cf95..051986e 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -31,12 +31,12 @@
priv_apps = " " +
"--show-annotation android.annotation.SystemApi\\(" +
- "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
"\\) "
module_libs = " " +
" --show-annotation android.annotation.SystemApi\\(" +
- "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
"\\) "
stubs_defaults {
@@ -48,6 +48,7 @@
stubs_defaults {
name: "framework-module-stubs-defaults-systemapi",
args: mainline_stubs_args + priv_apps,
+ srcs: [":framework-annotations"],
installable: false,
}
@@ -59,11 +60,13 @@
stubs_defaults {
name: "framework-module-api-defaults-module_libs_api",
args: mainline_stubs_args + module_libs,
+ srcs: [":framework-annotations"],
installable: false,
}
stubs_defaults {
name: "framework-module-stubs-defaults-module_libs_api",
args: mainline_stubs_args + module_libs + priv_apps,
+ srcs: [":framework-annotations"],
installable: false,
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 4c98b5f..62c90dfa 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -54,7 +54,8 @@
*
* @hide
*/
- public static final int REASON_RESTRAINED = JobProtoEnums.STOP_REASON_RESTRAINED; // 6.
+ public static final int REASON_RESTRICTED_BUCKET =
+ JobProtoEnums.STOP_REASON_RESTRICTED_BUCKET; // 6.
/**
* All the stop reason codes. This should be regarded as an immutable array at runtime.
@@ -72,7 +73,7 @@
REASON_TIMEOUT,
REASON_DEVICE_IDLE,
REASON_DEVICE_THERMAL,
- REASON_RESTRAINED,
+ REASON_RESTRICTED_BUCKET,
};
/**
@@ -88,7 +89,7 @@
case REASON_TIMEOUT: return "timeout";
case REASON_DEVICE_IDLE: return "device_idle";
case REASON_DEVICE_THERMAL: return "thermal";
- case REASON_RESTRAINED: return "restrained";
+ case REASON_RESTRICTED_BUCKET: return "restricted_bucket";
default: return "unknown:" + reasonCode;
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index c1e529f..b86aba6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1074,7 +1074,15 @@
uId, null, jobStatus.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED,
JobProtoEnums.STOP_REASON_CANCELLED, jobStatus.getStandbyBucket(),
- jobStatus.getJobId());
+ jobStatus.getJobId(),
+ jobStatus.hasChargingConstraint(),
+ jobStatus.hasBatteryNotLowConstraint(),
+ jobStatus.hasStorageNotLowConstraint(),
+ jobStatus.hasTimingDelayConstraint(),
+ jobStatus.hasDeadlineConstraint(),
+ jobStatus.hasIdleConstraint(),
+ jobStatus.hasConnectivityConstraint(),
+ jobStatus.hasContentTriggerConstraint());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -1955,9 +1963,19 @@
continue;
}
if (!running.isReady()) {
- serviceContext.cancelExecutingJobLocked(
- JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
- "cancelled due to unsatisfied constraints");
+ // If a restricted job doesn't have dynamic constraints satisfied, assume that's
+ // the reason the job is being stopped, instead of because of other constraints
+ // not being satisfied.
+ if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX
+ && !running.areDynamicConstraintsSatisfied()) {
+ serviceContext.cancelExecutingJobLocked(
+ JobParameters.REASON_RESTRICTED_BUCKET,
+ "cancelled due to restricted bucket");
+ } else {
+ serviceContext.cancelExecutingJobLocked(
+ JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
+ "cancelled due to unsatisfied constraints");
+ }
} else {
final JobRestriction restriction = checkIfRestricted(running);
if (restriction != null) {
@@ -3015,6 +3033,10 @@
return 0;
}
+ void resetExecutionQuota(@NonNull String pkgName, int userId) {
+ mQuotaController.clearAppStats(pkgName, userId);
+ }
+
void resetScheduleQuota() {
mQuotaTracker.clear();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 6becf04..9571708 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -66,6 +66,8 @@
return getJobState(pw);
case "heartbeat":
return doHeartbeat(pw);
+ case "reset-execution-quota":
+ return resetExecutionQuota(pw);
case "reset-schedule-quota":
return resetScheduleQuota(pw);
case "trigger-dock-state":
@@ -346,6 +348,40 @@
return -1;
}
+ private int resetExecutionQuota(PrintWriter pw) throws Exception {
+ checkPermission("reset execution quota");
+
+ int userId = UserHandle.USER_SYSTEM;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-u":
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+
+ default:
+ pw.println("Error: unknown option '" + opt + "'");
+ return -1;
+ }
+ }
+
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = ActivityManager.getCurrentUser();
+ }
+
+ final String pkgName = getNextArgRequired();
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInternal.resetExecutionQuota(pkgName, userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return 0;
+ }
+
private int resetScheduleQuota(PrintWriter pw) throws Exception {
checkPermission("reset schedule quota");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 26db4a3..565ed95 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -22,6 +22,7 @@
import android.app.job.IJobService;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
+import android.app.job.JobProtoEnums;
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
@@ -45,6 +46,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
@@ -273,9 +275,20 @@
return false;
}
mJobPackageTracker.noteActive(job);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
+ job.getSourceUid(), null, job.getBatteryName(),
+ FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__STARTED,
+ JobProtoEnums.STOP_REASON_UNKNOWN, job.getStandbyBucket(), job.getJobId(),
+ job.hasChargingConstraint(),
+ job.hasBatteryNotLowConstraint(),
+ job.hasStorageNotLowConstraint(),
+ job.hasTimingDelayConstraint(),
+ job.hasDeadlineConstraint(),
+ job.hasIdleConstraint(),
+ job.hasConnectivityConstraint(),
+ job.hasContentTriggerConstraint());
try {
- mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid(),
- job.getStandbyBucket(), job.getJobId());
+ mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
} catch (RemoteException e) {
// Whatever.
}
@@ -779,10 +792,21 @@
applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
+ completedJob.getSourceUid(), null, completedJob.getBatteryName(),
+ FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
+ mParams.getStopReason(), completedJob.getStandbyBucket(), completedJob.getJobId(),
+ completedJob.hasChargingConstraint(),
+ completedJob.hasBatteryNotLowConstraint(),
+ completedJob.hasStorageNotLowConstraint(),
+ completedJob.hasTimingDelayConstraint(),
+ completedJob.hasDeadlineConstraint(),
+ completedJob.hasIdleConstraint(),
+ completedJob.hasConnectivityConstraint(),
+ completedJob.hasContentTriggerConstraint());
try {
- mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
- mRunningJob.getSourceUid(), mParams.getStopReason(),
- mRunningJob.getStandbyBucket(), mRunningJob.getJobId());
+ mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
+ mParams.getStopReason());
} catch (RemoteException e) {
// Whatever.
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index aa3d74a..d7b1e07 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -16,6 +16,8 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
+
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
@@ -196,6 +198,9 @@
} else {
isActive = (activeState == KNOWN_ACTIVE);
}
+ if (isActive && jobStatus.getStandbyBucket() == NEVER_INDEX) {
+ Slog.wtf(TAG, "App became active but still in NEVER bucket");
+ }
boolean didChange = jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
didChange |= jobStatus.setUidActive(isActive);
return didChange;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index b63cc19..81dbc87 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1271,7 +1271,8 @@
// sessions (exempt from dynamic restrictions), we need the additional check to ensure
// that NEVER jobs don't run.
// TODO: cleanup quota and standby bucket management so we don't need the additional checks
- if ((!mReadyWithinQuota && !mReadyDynamicSatisfied) || standbyBucket == NEVER_INDEX) {
+ if ((!mReadyWithinQuota && !mReadyDynamicSatisfied)
+ || getEffectiveStandbyBucket() == NEVER_INDEX) {
return false;
}
// Deadline constraint trumps other constraints besides quota and dynamic (except for
@@ -1293,6 +1294,11 @@
CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
| CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
+ /** Returns true whenever all dynamically set constraints are satisfied. */
+ public boolean areDynamicConstraintsSatisfied() {
+ return mReadyDynamicSatisfied;
+ }
+
/**
* @return Whether the constraints set on this job are satisfied.
*/
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 8eefac8..4393a95 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -579,23 +579,7 @@
Slog.wtf(TAG, "Told app removed but given null package name.");
return;
}
- final int userId = UserHandle.getUserId(uid);
- mTrackedJobs.delete(userId, packageName);
- Timer timer = mPkgTimers.get(userId, packageName);
- if (timer != null) {
- if (timer.isActive()) {
- Slog.wtf(TAG, "onAppRemovedLocked called before Timer turned off.");
- timer.dropEverythingLocked();
- }
- mPkgTimers.delete(userId, packageName);
- }
- mTimingSessions.delete(userId, packageName);
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
- if (alarmListener != null) {
- mAlarmManager.cancel(alarmListener);
- mInQuotaAlarmListeners.delete(userId, packageName);
- }
- mExecutionStatsCache.delete(userId, packageName);
+ clearAppStats(packageName, UserHandle.getUserId(uid));
mForegroundUids.delete(uid);
mUidToPackageCache.remove(uid);
}
@@ -610,6 +594,26 @@
mUidToPackageCache.clear();
}
+ /** Drop all historical stats and stop tracking any active sessions for the specified app. */
+ public void clearAppStats(@NonNull String packageName, int userId) {
+ mTrackedJobs.delete(userId, packageName);
+ Timer timer = mPkgTimers.get(userId, packageName);
+ if (timer != null) {
+ if (timer.isActive()) {
+ Slog.e(TAG, "clearAppStats called before Timer turned off.");
+ timer.dropEverythingLocked();
+ }
+ mPkgTimers.delete(userId, packageName);
+ }
+ mTimingSessions.delete(userId, packageName);
+ QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
+ if (alarmListener != null) {
+ mAlarmManager.cancel(alarmListener);
+ mInQuotaAlarmListeners.delete(userId, packageName);
+ }
+ mExecutionStatsCache.delete(userId, packageName);
+ }
+
private boolean isUidInForeground(int uid) {
if (UserHandle.isCore(uid)) {
return true;
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index 2f3e2ac..4c96263 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -63,11 +63,7 @@
shared_libs: [
"libnativehelper", // Has stable abi - should not be copied into apex.
"liblog", // Has a stable abi - should not be copied into apex.
- ],
- static_libs: [
- //TODO: make shared - need libstatssocket to also live in the apex.
"libstatssocket",
- "libcutils", // TODO: remove - needed by libstatssocket
],
//TODO: is libc++_static correct?
stl: "libc++_static",
diff --git a/api/current.txt b/api/current.txt
index 1bd3aa7..2ec3bcc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2992,9 +2992,7 @@
method public int getNonInteractiveUiTimeoutMillis();
method public android.content.pm.ResolveInfo getResolveInfo();
method public String getSettingsActivityName();
- method @Nullable public android.graphics.drawable.Drawable loadAnimatedImage(@NonNull android.content.Context);
method public String loadDescription(android.content.pm.PackageManager);
- method @Nullable public String loadHtmlDescription(@NonNull android.content.pm.PackageManager);
method public CharSequence loadSummary(android.content.pm.PackageManager);
method public void setInteractiveUiTimeoutMillis(@IntRange(from=0) int);
method public void setNonInteractiveUiTimeoutMillis(@IntRange(from=0) int);
@@ -56980,6 +56978,7 @@
method @NonNull public android.view.inline.InlinePresentationSpec getPresentationSpec();
method @NonNull public String getSource();
method @NonNull public String getType();
+ method public boolean isPinned();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InlineSuggestionInfo> CREATOR;
field public static final String SOURCE_AUTOFILL = "android:autofill";
diff --git a/api/system-current.txt b/api/system-current.txt
index 63bb7d1..9a1aacd 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -9931,6 +9931,7 @@
method @NonNull public android.service.autofill.augmented.FillResponse build();
method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@Nullable android.service.autofill.augmented.FillWindow);
+ method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineActions(@Nullable java.util.List<android.service.autofill.InlinePresentation>);
method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@Nullable java.util.List<android.service.autofill.Dataset>);
}
@@ -9958,28 +9959,6 @@
method @NonNull @WorkerThread public abstract java.util.List<android.content.ContentValues> onRestoreApns(int);
}
- public abstract class CarrierMessagingServiceWrapper {
- ctor public CarrierMessagingServiceWrapper();
- method public boolean bindToCarrierMessagingService(@NonNull android.content.Context, @NonNull String);
- method public void disposeConnection(@NonNull android.content.Context);
- method public void downloadMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper);
- method public void filterSms(@NonNull android.service.carrier.MessagePdu, @NonNull String, int, int, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper);
- method public abstract void onServiceReady();
- method public void sendDataSms(@NonNull byte[], int, @NonNull String, int, int, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper);
- method public void sendMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper);
- method public void sendMultipartTextSms(@NonNull java.util.List<java.lang.String>, int, @NonNull String, int, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper);
- method public void sendTextSms(@NonNull String, int, @NonNull String, int, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper);
- }
-
- public abstract static class CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper {
- ctor public CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper();
- method public void onDownloadMmsComplete(int);
- method public void onFilterComplete(int);
- method public void onSendMmsComplete(int, @Nullable byte[]);
- method public void onSendMultipartSmsComplete(int, @Nullable int[]);
- method public void onSendSmsComplete(int, int);
- }
-
}
package android.service.contentcapture {
@@ -11929,45 +11908,6 @@
field public static final int SRVCC_STATE_HANDOVER_STARTED = 0; // 0x0
}
- public class TelephonyRegistryManager {
- method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
- method public void addOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
- method public void listenForSubscriber(int, @NonNull String, @NonNull String, @NonNull android.telephony.PhoneStateListener, int, boolean);
- method public void notifyActiveDataSubIdChanged(int);
- method public void notifyBarringInfoChanged(int, int, @NonNull android.telephony.BarringInfo);
- method public void notifyCallForwardingChanged(int, boolean);
- method public void notifyCallQualityChanged(int, int, @NonNull android.telephony.CallQuality, int);
- method public void notifyCallStateChanged(int, int, int, @Nullable String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
- method public void notifyCarrierNetworkChange(boolean);
- method public void notifyCellInfoChanged(int, @NonNull java.util.List<android.telephony.CellInfo>);
- method public void notifyCellLocation(int, @NonNull android.telephony.CellIdentity);
- method public void notifyDataActivationStateChanged(int, int, int);
- method public void notifyDataActivityChanged(int, int);
- method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
- method public void notifyDisconnectCause(int, int, int, int);
- method public void notifyDisplayInfoChanged(int, int, @NonNull android.telephony.DisplayInfo);
- method public void notifyEmergencyNumberList(int, int);
- method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
- method public void notifyMessageWaitingChanged(int, int, boolean);
- method public void notifyOpportunisticSubscriptionInfoChanged();
- method public void notifyOutgoingEmergencyCall(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
- method public void notifyOutgoingEmergencySms(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
- method public void notifyPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability);
- method public void notifyPreciseCallState(int, int, int, int, int);
- method public void notifyPreciseDataConnectionFailed(int, int, int, @Nullable String, int);
- method public void notifyRadioPowerStateChanged(int, int, int);
- method public void notifyRegistrationFailed(int, int, @NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
- method public void notifyServiceStateChanged(int, int, @NonNull android.telephony.ServiceState);
- method public void notifySignalStrengthChanged(int, int, @NonNull android.telephony.SignalStrength);
- method public void notifySrvccStateChanged(int, int);
- method public void notifySubscriptionInfoChanged();
- method public void notifyUserMobileDataStateChanged(int, int, boolean);
- method public void notifyVoiceActivationStateChanged(int, int, int);
- method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
- method public void removeOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
- }
-
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();
diff --git a/api/test-current.txt b/api/test-current.txt
index 0f8694f..f25f108 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3270,6 +3270,7 @@
method @NonNull public android.service.autofill.augmented.FillResponse build();
method @NonNull public android.service.autofill.augmented.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
method @NonNull public android.service.autofill.augmented.FillResponse.Builder setFillWindow(@Nullable android.service.autofill.augmented.FillWindow);
+ method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineActions(@Nullable java.util.List<android.service.autofill.InlinePresentation>);
method @NonNull public android.service.autofill.augmented.FillResponse.Builder setInlineSuggestions(@Nullable java.util.List<android.service.autofill.Dataset>);
}
@@ -5211,7 +5212,7 @@
}
public final class InlineSuggestionInfo implements android.os.Parcelable {
- method @NonNull public static android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(@NonNull android.view.inline.InlinePresentationSpec, @NonNull String, @Nullable String[]);
+ method @NonNull public static android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(@NonNull android.view.inline.InlinePresentationSpec, @NonNull String, @Nullable String[], @NonNull String, boolean);
}
public final class InlineSuggestionsResponse implements android.os.Parcelable {
diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
index 77a56e5..9439e1d 100644
--- a/cmds/incident_helper/src/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -237,33 +237,38 @@
Reader::Reader(const int fd)
{
mFile = fdopen(fd, "r");
+ mBuffer = new char[1024];
mStatus = mFile == nullptr ? "Invalid fd " + std::to_string(fd) : "";
}
Reader::~Reader()
{
if (mFile != nullptr) fclose(mFile);
+ free(mBuffer);
}
bool Reader::readLine(std::string* line) {
if (mFile == nullptr) return false;
- char* buf = nullptr;
size_t len = 0;
- ssize_t read = getline(&buf, &len, mFile);
+ ssize_t read = getline(&mBuffer, &len, mFile);
if (read != -1) {
- std::string s(buf);
+ std::string s(mBuffer);
line->assign(trim(s, DEFAULT_NEWLINE));
- } else if (errno == EINVAL) {
- mStatus = "Bad Argument";
+ return true;
}
- free(buf);
- return read != -1;
+ if (!feof(mFile)) {
+ mStatus = "Error reading file. Ferror: " + std::to_string(ferror(mFile));
+ }
+ return false;
}
bool Reader::ok(std::string* error) {
+ if (mStatus.empty()) {
+ return true;
+ }
error->assign(mStatus);
- return mStatus.empty();
+ return false;
}
// ==============================================================================
diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h
index 09dc8e6..5812c60 100644
--- a/cmds/incident_helper/src/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -117,6 +117,7 @@
private:
FILE* mFile;
+ char* mBuffer;
std::string mStatus;
};
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f36b855..51b9691 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -980,6 +980,17 @@
// The job id (as assigned by the app).
optional int32 job_id = 6;
+
+ // One flag for each of the API constraints defined by Jobscheduler. Does not include implcit
+ // constraints as they are always assumed to be set.
+ optional bool has_charging_constraint = 7;
+ optional bool has_battery_not_low_constraint = 8;
+ optional bool has_storage_not_low_constraint = 9;
+ optional bool has_timing_delay_constraint = 10;
+ optional bool has_deadline_constraint = 11;
+ optional bool has_idle_constraint = 12;
+ optional bool has_connectivity_constraint = 13;
+ optional bool has_content_trigger_constraint = 14;
}
/**
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 316a018..ca22bf4 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -16,19 +16,19 @@
package android.accessibilityservice;
+import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText;
+import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.StringRes;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -62,7 +62,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.regex.Pattern;
/**
* This class describes an {@link AccessibilityService}. The system notifies an
@@ -554,13 +553,6 @@
*/
private int mHtmlDescriptionRes;
- // Used for html description of accessibility service. The <img> src tag must follow the
- // prefix rule. e.g. <img src="R.drawable.fileName"/>
- private static final String IMG_PREFIX = "R.drawable.";
- private static final String ANCHOR_TAG = "a";
- private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
- Collections.singletonList(ANCHOR_TAG));
-
/**
* Creates a new instance.
*/
@@ -810,6 +802,8 @@
* </p>
* @return The animated image drawable, or null if the resource is invalid or the image
* exceed the screen size.
+ *
+ * @hide
*/
@Nullable
public Drawable loadAnimatedImage(@NonNull Context context) {
@@ -937,6 +931,8 @@
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
* </p>
* @return The localized and restricted html description.
+ *
+ * @hide
*/
@Nullable
public String loadHtmlDescription(@NonNull PackageManager packageManager) {
@@ -1421,103 +1417,4 @@
return new AccessibilityServiceInfo[size];
}
};
-
- /**
- * Gets the filtered html string for
- * {@link android.accessibilityservice.AccessibilityServiceInfo} and
- * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
- * the <img> tag which do not meet the custom specification and the <a> tag.
- *
- * @param text the target text is html format.
- * @return the filtered html string.
- *
- * @hide
- */
- public static @NonNull String getFilteredHtmlText(@NonNull String text) {
- final String replacementStart = "<invalidtag ";
- final String replacementEnd = "</invalidtag>";
-
- for (String tag : UNSUPPORTED_TAG_LIST) {
- final String regexStart = "(?i)<" + tag + "(\\s+|>)";
- final String regexEnd = "(?i)</" + tag + "\\s*>";
- text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
- text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
- }
-
- final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
- text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
- replacementStart);
-
- return text;
- }
-
- /**
- * Loads the animated image for
- * {@link android.accessibilityservice.AccessibilityServiceInfo} and
- * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
- * whether to exceed the screen size.
- *
- * @param context the current context.
- * @param applicationInfo the current application.
- * @param resId the animated image resource id.
- * @return the animated image which is safe.
- *
- * @hide
- */
- @Nullable
- public static Drawable loadSafeAnimatedImage(@NonNull Context context,
- @NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
- if (resId == /* invalid */ 0) {
- return null;
- }
-
- final PackageManager packageManager = context.getPackageManager();
- final String packageName = applicationInfo.packageName;
- final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
- applicationInfo);
- if (bannerDrawable == null) {
- return null;
- }
-
- final boolean isImageWidthOverScreenLength =
- bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
- final boolean isImageHeightOverScreenLength =
- bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
-
- return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
- ? null
- : bannerDrawable;
- }
-
- /**
- * Gets the width of the screen.
- *
- * @param context the current context.
- * @return the width of the screen in term of pixels.
- *
- * @hide
- */
- private static int getScreenWidthPixels(@NonNull Context context) {
- final Resources resources = context.getResources();
- final int screenWidthDp = resources.getConfiguration().screenWidthDp;
-
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
- resources.getDisplayMetrics()));
- }
-
- /**
- * Gets the height of the screen.
- *
- * @param context the current context.
- * @return the height of the screen in term of pixels.
- *
- * @hide
- */
- private static int getScreenHeightPixels(@NonNull Context context) {
- final Resources resources = context.getResources();
- final int screenHeightDp = resources.getConfiguration().screenHeightDp;
-
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
- resources.getDisplayMetrics()));
- }
}
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index a812f29..d2bdf80 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -16,14 +16,15 @@
package android.accessibilityservice;
+import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText;
+import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.StringRes;
import android.content.ComponentName;
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.res.Resources;
import android.content.res.TypedArray;
@@ -31,17 +32,12 @@
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Pattern;
/**
* Activities of interest to users with accessibility needs may request to be targets of the
@@ -94,13 +90,6 @@
*/
private final int mHtmlDescriptionRes;
- // Used for html description of accessibility service. The <img> src tag must follow the
- // prefix rule. e.g. <img src="R.drawable.fileName"/>
- private static final String IMG_PREFIX = "R.drawable.";
- private static final String ANCHOR_TAG = "a";
- private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
- Collections.singletonList(ANCHOR_TAG));
-
/**
* Creates a new instance.
*
@@ -221,6 +210,8 @@
*
* @return The animated image drawable, or null if the resource is invalid or the image
* exceed the screen size.
+ *
+ * @hide
*/
@Nullable
public Drawable loadAnimatedImage(@NonNull Context context) {
@@ -236,6 +227,8 @@
* It filters the <img> tag which do not meet the custom specification and the <a> tag.
*
* @return The localized and restricted html description.
+ *
+ * @hide
*/
@Nullable
public String loadHtmlDescription(@NonNull PackageManager packageManager) {
@@ -305,103 +298,4 @@
stringBuilder.append("]");
return stringBuilder.toString();
}
-
- /**
- * Gets the filtered html string for
- * {@link android.accessibilityservice.AccessibilityServiceInfo} and
- * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
- * the <img> tag which do not meet the custom specification and the <a> tag.
- *
- * @param text the target text is html format.
- * @return the filtered html string.
- *
- * @hide
- */
- public static @NonNull String getFilteredHtmlText(@NonNull String text) {
- final String replacementStart = "<invalidtag ";
- final String replacementEnd = "</invalidtag>";
-
- for (String tag : UNSUPPORTED_TAG_LIST) {
- final String regexStart = "(?i)<" + tag + "(\\s+|>)";
- final String regexEnd = "(?i)</" + tag + "\\s*>";
- text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
- text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
- }
-
- final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
- text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
- replacementStart);
-
- return text;
- }
-
- /**
- * Loads the animated image for
- * {@link android.accessibilityservice.AccessibilityServiceInfo} and
- * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
- * whether to exceed the screen size.
- *
- * @param context the current context.
- * @param applicationInfo the current application.
- * @param resId the animated image resource id.
- * @return the animated image which is safe.
- *
- * @hide
- */
- @Nullable
- public static Drawable loadSafeAnimatedImage(@NonNull Context context,
- @NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
- if (resId == /* invalid */ 0) {
- return null;
- }
-
- final PackageManager packageManager = context.getPackageManager();
- final String packageName = applicationInfo.packageName;
- final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
- applicationInfo);
- if (bannerDrawable == null) {
- return null;
- }
-
- final boolean isImageWidthOverScreenLength =
- bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
- final boolean isImageHeightOverScreenLength =
- bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
-
- return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
- ? null
- : bannerDrawable;
- }
-
- /**
- * Gets the width of the screen.
- *
- * @param context the current context.
- * @return the width of the screen in term of pixels.
- *
- * @hide
- */
- private static int getScreenWidthPixels(@NonNull Context context) {
- final Resources resources = context.getResources();
- final int screenWidthDp = resources.getConfiguration().screenWidthDp;
-
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
- resources.getDisplayMetrics()));
- }
-
- /**
- * Gets the height of the screen.
- *
- * @param context the current context.
- * @return the height of the screen in term of pixels.
- *
- * @hide
- */
- private static int getScreenHeightPixels(@NonNull Context context) {
- final Resources resources = context.getResources();
- final int screenHeightDp = resources.getConfiguration().screenHeightDp;
-
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
- resources.getDisplayMetrics()));
- }
}
diff --git a/core/java/android/accessibilityservice/util/AccessibilityUtils.java b/core/java/android/accessibilityservice/util/AccessibilityUtils.java
new file mode 100644
index 0000000..fa32bb2
--- /dev/null
+++ b/core/java/android/accessibilityservice/util/AccessibilityUtils.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 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.accessibilityservice.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Collection of utilities for accessibility service.
+ *
+ * @hide
+ */
+public final class AccessibilityUtils {
+ private AccessibilityUtils() {}
+
+ // Used for html description of accessibility service. The <img> src tag must follow the
+ // prefix rule. e.g. <img src="R.drawable.fileName"/>
+ private static final String IMG_PREFIX = "R.drawable.";
+ private static final String ANCHOR_TAG = "a";
+ private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
+ Collections.singletonList(ANCHOR_TAG));
+
+ /**
+ * Gets the filtered html string for
+ * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+ * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
+ * the <img> tag which do not meet the custom specification and the <a> tag.
+ *
+ * @param text the target text is html format.
+ * @return the filtered html string.
+ */
+ public static @NonNull String getFilteredHtmlText(@NonNull String text) {
+ final String replacementStart = "<invalidtag ";
+ final String replacementEnd = "</invalidtag>";
+
+ for (String tag : UNSUPPORTED_TAG_LIST) {
+ final String regexStart = "(?i)<" + tag + "(\\s+|>)";
+ final String regexEnd = "(?i)</" + tag + "\\s*>";
+ text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
+ text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
+ }
+
+ final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
+ text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
+ replacementStart);
+
+ return text;
+ }
+
+ /**
+ * Loads the animated image for
+ * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+ * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
+ * whether to exceed the screen size.
+ *
+ * @param context the current context.
+ * @param applicationInfo the current application.
+ * @param resId the animated image resource id.
+ * @return the animated image which is safe.
+ */
+ @Nullable
+ public static Drawable loadSafeAnimatedImage(@NonNull Context context,
+ @NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
+ if (resId == /* invalid */ 0) {
+ return null;
+ }
+
+ final PackageManager packageManager = context.getPackageManager();
+ final String packageName = applicationInfo.packageName;
+ final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
+ applicationInfo);
+ if (bannerDrawable == null) {
+ return null;
+ }
+
+ final boolean isImageWidthOverScreenLength =
+ bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
+ final boolean isImageHeightOverScreenLength =
+ bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
+
+ return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
+ ? null
+ : bannerDrawable;
+ }
+
+ /**
+ * Gets the width of the screen.
+ *
+ * @param context the current context.
+ * @return the width of the screen in term of pixels.
+ */
+ private static int getScreenWidthPixels(@NonNull Context context) {
+ final Resources resources = context.getResources();
+ final int screenWidthDp = resources.getConfiguration().screenWidthDp;
+
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
+ resources.getDisplayMetrics()));
+ }
+
+ /**
+ * Gets the height of the screen.
+ *
+ * @param context the current context.
+ * @return the height of the screen in term of pixels.
+ */
+ private static int getScreenHeightPixels(@NonNull Context context) {
+ final Resources resources = context.getResources();
+ final int screenHeightDp = resources.getConfiguration().screenHeightDp;
+
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
+ resources.getDisplayMetrics()));
+ }
+}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 4f41f8b..969ea70 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -675,7 +675,7 @@
@Override
public int checkPermission(String permName, String pkgName) {
return PermissionManager
- .checkPackageNamePermission(permName, pkgName);
+ .checkPackageNamePermission(permName, pkgName, getUserId());
}
@Override
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
new file mode 100644
index 0000000..9ef63f6
--- /dev/null
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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.app.compat;
+
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.internal.compat.IPlatformCompat;
+
+/**
+ * Handles caching of calls to {@link com.android.internal.compat.IPlatformCompat}
+ * @hide
+ */
+public final class ChangeIdStateCache
+ extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
+ private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
+ private static final int MAX_ENTRIES = 20;
+ private static boolean sDisabled = false;
+
+ /** @hide */
+ public ChangeIdStateCache() {
+ super(MAX_ENTRIES, CACHE_KEY);
+ }
+
+ /**
+ * Disable cache.
+ *
+ * <p>Should only be used in unit tests.
+ * @hide
+ */
+ public static void disable() {
+ sDisabled = true;
+ }
+
+ /**
+ * Invalidate the cache.
+ *
+ * <p>Can only be called by the system server process.
+ * @hide
+ */
+ public static void invalidate() {
+ if (!sDisabled) {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY);
+ }
+ }
+
+ @Override
+ protected Boolean recompute(ChangeIdStateQuery query) {
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (query.type == ChangeIdStateQuery.QUERY_BY_PACKAGE_NAME) {
+ return platformCompat.isChangeEnabledByPackageName(query.changeId,
+ query.packageName,
+ query.userId);
+ } else if (query.type == ChangeIdStateQuery.QUERY_BY_UID) {
+ return platformCompat.isChangeEnabledByUid(query.changeId, query.uid);
+ } else {
+ throw new IllegalArgumentException("Invalid query type: " + query.type);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ throw new IllegalStateException("Could not recompute value!");
+ }
+}
diff --git a/core/java/android/app/compat/ChangeIdStateQuery.java b/core/java/android/app/compat/ChangeIdStateQuery.java
new file mode 100644
index 0000000..3c245b1
--- /dev/null
+++ b/core/java/android/app/compat/ChangeIdStateQuery.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 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.app.compat;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.Immutable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+
+/**
+ * A key type for caching calls to {@link com.android.internal.compat.IPlatformCompat}
+ *
+ * <p>For {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByPackageName}
+ * and {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByUid}
+ *
+ * @hide
+ */
+@Immutable
+final class ChangeIdStateQuery {
+
+ static final int QUERY_BY_PACKAGE_NAME = 0;
+ static final int QUERY_BY_UID = 1;
+ @IntDef({QUERY_BY_PACKAGE_NAME, QUERY_BY_UID})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryType {}
+
+ public @QueryType int type;
+ public long changeId;
+ public String packageName;
+ public int uid;
+ public int userId;
+
+ private ChangeIdStateQuery(@QueryType int type, long changeId, String packageName,
+ int uid, int userId) {
+ this.type = type;
+ this.changeId = changeId;
+ this.packageName = packageName;
+ this.uid = uid;
+ this.userId = userId;
+ }
+
+ static ChangeIdStateQuery byPackageName(long changeId, @NonNull String packageName,
+ int userId) {
+ return new ChangeIdStateQuery(QUERY_BY_PACKAGE_NAME, changeId, packageName, 0, userId);
+ }
+
+ static ChangeIdStateQuery byUid(long changeId, int uid) {
+ return new ChangeIdStateQuery(QUERY_BY_UID, changeId, null, uid, 0);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if ((other == null) || !(other instanceof ChangeIdStateQuery)) {
+ return false;
+ }
+ final ChangeIdStateQuery that = (ChangeIdStateQuery) other;
+ return this.type == that.type
+ && this.changeId == that.changeId
+ && Objects.equals(this.packageName, that.packageName)
+ && this.uid == that.uid
+ && this.userId == that.userId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, changeId, packageName, uid, userId);
+ }
+}
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index e289a27..0d5e45f 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -19,14 +19,8 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.compat.Compatibility;
-import android.content.Context;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
-import com.android.internal.compat.IPlatformCompat;
-
/**
* CompatChanges APIs - to be used by platform code only (including mainline
* modules).
@@ -35,6 +29,7 @@
*/
@SystemApi
public final class CompatChanges {
+ private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache();
private CompatChanges() {}
/**
@@ -69,17 +64,8 @@
*/
public static boolean isChangeEnabled(long changeId, @NonNull String packageName,
@NonNull UserHandle user) {
- IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- final long token = Binder.clearCallingIdentity();
- try {
- return platformCompat.isChangeEnabledByPackageName(changeId, packageName,
- user.getIdentifier());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(changeId, packageName,
+ user.getIdentifier()));
}
/**
@@ -101,15 +87,7 @@
* @return {@code true} if the change is enabled for the current app.
*/
public static boolean isChangeEnabled(long changeId, int uid) {
- IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- final long token = Binder.clearCallingIdentity();
- try {
- return platformCompat.isChangeEnabledByUid(changeId, uid);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid));
}
+
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index b748cfa..c7f42cb 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -3827,6 +3827,18 @@
return queryArgs;
}
+ /** @hide */
+ public static @NonNull Bundle includeSqlSelectionArgs(@NonNull Bundle queryArgs,
+ @Nullable String selection, @Nullable String[] selectionArgs) {
+ if (selection != null) {
+ queryArgs.putString(QUERY_ARG_SQL_SELECTION, selection);
+ }
+ if (selectionArgs != null) {
+ queryArgs.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
+ }
+ return queryArgs;
+ }
+
/**
* Returns structured sort args formatted as an SQL sort clause.
*
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 4246b84..34cc856 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -16,6 +16,7 @@
package android.database;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentValues;
@@ -1548,4 +1549,24 @@
}
return -1;
}
+
+ /**
+ * Escape the given argument for use in a {@code LIKE} statement.
+ * @hide
+ */
+ public static String escapeForLike(@NonNull String arg) {
+ // Shamelessly borrowed from com.android.providers.media.util.DatabaseUtils
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < arg.length(); i++) {
+ final char c = arg.charAt(i);
+ switch (c) {
+ case '%': sb.append('\\');
+ break;
+ case '_': sb.append('\\');
+ break;
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 743ce7b..85ef4a3 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -131,9 +131,17 @@
}
/**
- * Return the list of combinations of currently connected camera devices identifiers, which
+ * Return the set of combinations of currently connected camera device identifiers, which
* support configuring camera device sessions concurrently.
*
+ * <p>The devices in these combinations can be concurrently configured by the same
+ * client camera application. Using these camera devices concurrently by two different
+ * applications is not guaranteed to be supported, however.</p>
+ *
+ * <p>Each device in a combination, is guaranteed to support stream combinations which may be
+ * obtained by querying {@link #getCameraCharacteristics} for the key
+ * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}.</p>
+ *
* <p>The set of combinations may include camera devices that may be in use by other camera API
* clients.</p>
*
@@ -174,7 +182,7 @@
* to be used for exploring the entire space of supported concurrent stream combinations. The
* available mandatory concurrent stream combinations may be obtained by querying
* {@link #getCameraCharacteristics} for the key
- * SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS. </p>
+ * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}. </p>
*
* <p>Note that session parameters will be ignored and calls to
* {@link SessionConfiguration#setSessionParameters} are not required.</p>
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
index ef76c62..bf641d7 100644
--- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
@@ -28,6 +28,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Slog;
import android.util.Xml;
@@ -43,6 +44,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
/**
* Enrollment information about the different available keyphrases.
@@ -116,7 +118,12 @@
/**
* List of available keyphrases.
*/
- final private KeyphraseMetadata[] mKeyphrases;
+ private final KeyphraseMetadata[] mKeyphrases;
+
+ /**
+ * Set of UIDs associated with the detected enrollment applications.
+ */
+ private final Set<Integer> mEnrollmentApplicationUids;
/**
* Map between KeyphraseMetadata and the package name of the enrollment app that provides it.
@@ -136,11 +143,13 @@
mParseError = "No enrollment applications found";
mKeyphrasePackageMap = Collections.<KeyphraseMetadata, String>emptyMap();
mKeyphrases = null;
+ mEnrollmentApplicationUids = Collections.emptySet();
return;
}
List<String> parseErrors = new LinkedList<String>();
mKeyphrasePackageMap = new HashMap<KeyphraseMetadata, String>();
+ mEnrollmentApplicationUids = new ArraySet<>();
for (ResolveInfo ri : ris) {
try {
ApplicationInfo ai = pm.getApplicationInfo(
@@ -162,6 +171,7 @@
getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors);
if (metadata != null) {
mKeyphrasePackageMap.put(metadata, ai.packageName);
+ mEnrollmentApplicationUids.add(ai.uid);
}
} catch (PackageManager.NameNotFoundException e) {
String error = "error parsing voice enrollment meta-data for "
@@ -372,9 +382,22 @@
return null;
}
+ /**
+ * Tests if the input UID matches a supported enrollment application.
+ *
+ * @param uid UID of the caller to test against.
+ * @return Returns true if input uid matches the uid of a supported enrollment application.
+ * False if not.
+ */
+ public boolean isUidSupportedEnrollmentApplication(int uid) {
+ Log.d(TAG, "isUidSupportedEnrollmentApplication: " + toString());
+ return mEnrollmentApplicationUids.contains(uid);
+ }
+
@Override
public String toString() {
- return "KeyphraseEnrollmentInfo [Keyphrases=" + mKeyphrasePackageMap.toString()
+ return "KeyphraseEnrollmentInfo [KeyphrasePackageMap=" + mKeyphrasePackageMap.toString()
+ + ", enrollmentApplicationUids=" + mEnrollmentApplicationUids.toString()
+ ", ParseError=" + mParseError + "]";
}
}
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 9327b24..e9de274 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -53,6 +53,7 @@
private static final int DO_FINISH_SESSION = 110;
private static final int DO_VIEW_CLICKED = 115;
private static final int DO_NOTIFY_IME_HIDDEN = 120;
+ private static final int DO_REMOVE_IME_SURFACE = 130;
@UnsupportedAppUsage
HandlerCaller mCaller;
@@ -136,6 +137,10 @@
mInputMethodSession.notifyImeHidden();
return;
}
+ case DO_REMOVE_IME_SURFACE: {
+ mInputMethodSession.removeImeSurface();
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -184,6 +189,11 @@
}
@Override
+ public void removeImeSurface() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_IME_SURFACE));
+ }
+
+ @Override
public void updateCursor(Rect newCursor) {
mCaller.executeOrSendMessage(
mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index f0b1eaa..b52b437 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -219,22 +219,29 @@
case DO_REVOKE_SESSION:
inputMethod.revokeSession((InputMethodSession)msg.obj);
return;
- case DO_SHOW_SOFT_INPUT:
- SomeArgs args = (SomeArgs)msg.obj;
+ case DO_SHOW_SOFT_INPUT: {
+ final SomeArgs args = (SomeArgs)msg.obj;
inputMethod.showSoftInputWithToken(
msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+ args.recycle();
return;
- case DO_HIDE_SOFT_INPUT:
- inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
+ }
+ case DO_HIDE_SOFT_INPUT: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
+ (IBinder) args.arg1);
+ args.recycle();
return;
+ }
case DO_CHANGE_INPUTMETHOD_SUBTYPE:
inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
return;
case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
- args = (SomeArgs) msg.obj;
+ final SomeArgs args = (SomeArgs) msg.obj;
inputMethod.onCreateInlineSuggestionsRequest(
(InlineSuggestionsRequestInfo) args.arg1,
(IInlineSuggestionsRequestCallback) args.arg2);
+ args.recycle();
return;
}
@@ -380,9 +387,9 @@
@BinderThread
@Override
- public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
- flags, resultReceiver));
+ public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT,
+ flags, hideInputToken, resultReceiver));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 20a4ab3..27839e7 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -459,6 +459,16 @@
*/
private IBinder mCurShowInputToken;
+ /**
+ * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#hideSoftInput}
+ * The original app window token is passed from client app window.
+ * {@link com.android.server.inputmethod.InputMethodManagerService} creates a unique dummy
+ * token to identify this window.
+ * This dummy token is only valid for a single call to {@link InputMethodImpl#hideSoftInput},
+ * after which it is set {@code null} until next call.
+ */
+ private IBinder mCurHideInputToken;
+
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
onComputeInsets(mTmpInsets);
if (isExtractViewShown()) {
@@ -500,6 +510,7 @@
public class InputMethodImpl extends AbstractInputMethodImpl {
private boolean mSystemCallingShowSoftInput;
+ private boolean mSystemCallingHideSoftInput;
/**
* {@inheritDoc}
@@ -636,11 +647,32 @@
/**
* {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
+ public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+ IBinder hideInputToken) {
+ mSystemCallingHideSoftInput = true;
+ mCurHideInputToken = hideInputToken;
+ hideSoftInput(flags, resultReceiver);
+ mCurHideInputToken = null;
+ mSystemCallingHideSoftInput = false;
+ }
+
+ /**
+ * {@inheritDoc}
*/
@MainThread
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "hideSoftInput()");
+ if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
+ && !mSystemCallingHideSoftInput) {
+ Log.e(TAG, "IME shouldn't call hideSoftInput on itself."
+ + " Use requestHideSelf(int) itself");
+ return;
+ }
final boolean wasVisible = mIsPreRendered
? mDecorViewVisible && mWindowVisible : isInputViewShown();
applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */);
@@ -738,6 +770,15 @@
public void setCurrentShowInputToken(IBinder showInputToken) {
mCurShowInputToken = showInputToken;
}
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ public void setCurrentHideInputToken(IBinder hideInputToken) {
+ mCurHideInputToken = hideInputToken;
+ }
}
// TODO(b/137800469): Add detailed docs explaining the inline suggestions process.
@@ -814,6 +855,13 @@
onPreRenderedWindowVisibilityChanged(false /* setVisible */);
}
+ private void removeImeSurface() {
+ if (!mShowInputRequested && !mWindowVisible) {
+ // hiding a window removes its surface.
+ mWindow.hide();
+ }
+ }
+
private void setImeWindowStatus(int visibilityFlags, int backDisposition) {
mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition);
}
@@ -932,6 +980,14 @@
public final void notifyImeHidden() {
InputMethodService.this.notifyImeHidden();
}
+
+ /**
+ * Notify IME that surface can be now removed.
+ * @hide
+ */
+ public final void removeImeSurface() {
+ InputMethodService.this.removeImeSurface();
+ }
}
/**
@@ -2157,7 +2213,8 @@
if (!isVisibilityAppliedUsingInsetsConsumer()) {
return;
}
- mPrivOps.applyImeVisibility(mCurShowInputToken, setVisible);
+ mPrivOps.applyImeVisibility(setVisible
+ ? mCurShowInputToken : mCurHideInputToken, setVisible);
}
private boolean isVisibilityAppliedUsingInsetsConsumer() {
diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
index 31c948a..ef138a0 100644
--- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
+++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
@@ -296,6 +296,12 @@
// no-op for multi-session since IME is responsible controlling navigation bar buttons.
reportNotSupported();
}
+
+ @Override
+ public void removeImeSurface() {
+ // no-op for multi-session
+ reportNotSupported();
+ }
}
private static final class MultiClientInputMethodSessionImpl
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 5d6dc7b..0bd211d 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -561,21 +561,24 @@
private static final class PackageNamePermissionQuery {
final String permName;
final String pkgName;
+ final int uid;
- PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName) {
+ PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName, int uid) {
this.permName = permName;
this.pkgName = pkgName;
+ this.uid = uid;
}
@Override
public String toString() {
- return String.format("PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s\")",
- pkgName, permName);
+ return String.format(
+ "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s, uid=%s\")",
+ pkgName, permName, uid);
}
@Override
public int hashCode() {
- return Objects.hashCode(permName) * 13 + Objects.hashCode(pkgName);
+ return Objects.hash(permName, pkgName, uid);
}
@Override
@@ -590,15 +593,17 @@
return false;
}
return Objects.equals(permName, other.permName)
- && Objects.equals(pkgName, other.pkgName);
+ && Objects.equals(pkgName, other.pkgName)
+ && uid == other.uid;
}
}
/* @hide */
- private static int checkPackageNamePermissionUncached(String permName, String pkgName) {
+ private static int checkPackageNamePermissionUncached(
+ String permName, String pkgName, int uid) {
try {
return ActivityThread.getPermissionManager().checkPermission(
- permName, pkgName, UserHandle.myUserId());
+ permName, pkgName, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -611,7 +616,8 @@
16, CACHE_KEY_PACKAGE_INFO) {
@Override
protected Integer recompute(PackageNamePermissionQuery query) {
- return checkPackageNamePermissionUncached(query.permName, query.pkgName);
+ return checkPackageNamePermissionUncached(
+ query.permName, query.pkgName, query.uid);
}
};
@@ -620,9 +626,9 @@
*
* @hide
*/
- public static int checkPackageNamePermission(String permName, String pkgName) {
+ public static int checkPackageNamePermission(String permName, String pkgName, int uid) {
return sPackageNamePermissionCache.query(
- new PackageNamePermissionQuery(permName, pkgName));
+ new PackageNamePermissionQuery(permName, pkgName, uid));
}
/**
diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java
index fb8406e..a9addba 100644
--- a/core/java/android/service/autofill/InlinePresentation.java
+++ b/core/java/android/service/autofill/InlinePresentation.java
@@ -17,6 +17,7 @@
package android.service.autofill;
import android.annotation.NonNull;
+import android.annotation.Size;
import android.app.slice.Slice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -24,6 +25,8 @@
import com.android.internal.util.DataClass;
+import java.util.List;
+
/**
* Wrapper class holding a {@link Slice} and an {@link InlinePresentationSpec} for rendering UI
* for an Inline Suggestion.
@@ -50,6 +53,18 @@
*/
private final boolean mPinned;
+ /**
+ * Returns the autofill hints set in the slice.
+ *
+ * @hide
+ */
+ @NonNull
+ @Size(min = 0)
+ public String[] getAutofillHints() {
+ List<String> hints = mSlice.getHints();
+ return hints.toArray(new String[hints.size()]);
+ }
+
// Code below generated by codegen v1.0.14.
@@ -214,10 +229,10 @@
};
@DataClass.Generated(
- time = 1579726472535L,
+ time = 1582753782651L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java",
- inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
+ inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size(min=0L) java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java
index 68ba63a..b7fdf5a 100644
--- a/core/java/android/service/autofill/augmented/FillResponse.java
+++ b/core/java/android/service/autofill/augmented/FillResponse.java
@@ -21,6 +21,7 @@
import android.annotation.TestApi;
import android.os.Bundle;
import android.service.autofill.Dataset;
+import android.service.autofill.InlinePresentation;
import com.android.internal.util.DataClass;
@@ -52,6 +53,13 @@
private @Nullable List<Dataset> mInlineSuggestions;
/**
+ * The {@link InlinePresentation}s representing the inline actions. Defaults to null if no
+ * inline actions are provided.
+ */
+ @DataClass.PluralOf("inlineAction")
+ private @Nullable List<InlinePresentation> mInlineActions;
+
+ /**
* The client state that {@link AugmentedAutofillService} implementation can put anything in to
* identify the request and the response when calling
* {@link AugmentedAutofillService#getFillEventHistory()}.
@@ -66,6 +74,10 @@
return null;
}
+ private static List<InlinePresentation> defaultInlineActions() {
+ return null;
+ }
+
private static Bundle defaultClientState() {
return null;
}
@@ -74,6 +86,7 @@
/** @hide */
abstract static class BaseBuilder {
abstract FillResponse.Builder addInlineSuggestion(@NonNull Dataset value);
+ abstract FillResponse.Builder addInlineAction(@NonNull InlinePresentation value);
}
@@ -95,9 +108,11 @@
/* package-private */ FillResponse(
@Nullable FillWindow fillWindow,
@Nullable List<Dataset> inlineSuggestions,
+ @Nullable List<InlinePresentation> inlineActions,
@Nullable Bundle clientState) {
this.mFillWindow = fillWindow;
this.mInlineSuggestions = inlineSuggestions;
+ this.mInlineActions = inlineActions;
this.mClientState = clientState;
// onConstructed(); // You can define this method to get a callback
@@ -125,6 +140,17 @@
}
/**
+ * The {@link InlinePresentation}s representing the inline actions. Defaults to null if no
+ * inline actions are provided.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable List<InlinePresentation> getInlineActions() {
+ return mInlineActions;
+ }
+
+ /**
* The client state that {@link AugmentedAutofillService} implementation can put anything in to
* identify the request and the response when calling
* {@link AugmentedAutofillService#getFillEventHistory()}.
@@ -145,6 +171,7 @@
private @Nullable FillWindow mFillWindow;
private @Nullable List<Dataset> mInlineSuggestions;
+ private @Nullable List<InlinePresentation> mInlineActions;
private @Nullable Bundle mClientState;
private long mBuilderFieldsSet = 0L;
@@ -185,6 +212,27 @@
}
/**
+ * The {@link InlinePresentation}s representing the inline actions. Defaults to null if no
+ * inline actions are provided.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setInlineActions(@Nullable List<InlinePresentation> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mInlineActions = value;
+ return this;
+ }
+
+ /** @see #setInlineActions */
+ @DataClass.Generated.Member
+ @Override
+ @NonNull FillResponse.Builder addInlineAction(@NonNull InlinePresentation value) {
+ if (mInlineActions == null) setInlineActions(new ArrayList<>());
+ mInlineActions.add(value);
+ return this;
+ }
+
+ /**
* The client state that {@link AugmentedAutofillService} implementation can put anything in to
* identify the request and the response when calling
* {@link AugmentedAutofillService#getFillEventHistory()}.
@@ -192,7 +240,7 @@
@DataClass.Generated.Member
public @NonNull Builder setClientState(@Nullable Bundle value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x4;
+ mBuilderFieldsSet |= 0x8;
mClientState = value;
return this;
}
@@ -200,7 +248,7 @@
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull FillResponse build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x8; // Mark builder used
+ mBuilderFieldsSet |= 0x10; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mFillWindow = defaultFillWindow();
@@ -209,17 +257,21 @@
mInlineSuggestions = defaultInlineSuggestions();
}
if ((mBuilderFieldsSet & 0x4) == 0) {
+ mInlineActions = defaultInlineActions();
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
mClientState = defaultClientState();
}
FillResponse o = new FillResponse(
mFillWindow,
mInlineSuggestions,
+ mInlineActions,
mClientState);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x8) != 0) {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -227,10 +279,10 @@
}
@DataClass.Generated(
- time = 1580335256422L,
+ time = 1582682935951L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/service/autofill/augmented/FillResponse.java",
- inputSignatures = "private @android.annotation.Nullable android.service.autofill.augmented.FillWindow mFillWindow\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineSuggestion\") @android.annotation.Nullable java.util.List<android.service.autofill.Dataset> mInlineSuggestions\nprivate @android.annotation.Nullable android.os.Bundle mClientState\nprivate static android.service.autofill.augmented.FillWindow defaultFillWindow()\nprivate static java.util.List<android.service.autofill.Dataset> defaultInlineSuggestions()\nprivate static android.os.Bundle defaultClientState()\nclass FillResponse extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genHiddenGetters=true)\nabstract android.service.autofill.augmented.FillResponse.Builder addInlineSuggestion(android.service.autofill.Dataset)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "private @android.annotation.Nullable android.service.autofill.augmented.FillWindow mFillWindow\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineSuggestion\") @android.annotation.Nullable java.util.List<android.service.autofill.Dataset> mInlineSuggestions\nprivate @com.android.internal.util.DataClass.PluralOf(\"inlineAction\") @android.annotation.Nullable java.util.List<android.service.autofill.InlinePresentation> mInlineActions\nprivate @android.annotation.Nullable android.os.Bundle mClientState\nprivate static android.service.autofill.augmented.FillWindow defaultFillWindow()\nprivate static java.util.List<android.service.autofill.Dataset> defaultInlineSuggestions()\nprivate static java.util.List<android.service.autofill.InlinePresentation> defaultInlineActions()\nprivate static android.os.Bundle defaultClientState()\nclass FillResponse extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genHiddenGetters=true)\nabstract android.service.autofill.augmented.FillResponse.Builder addInlineSuggestion(android.service.autofill.Dataset)\nabstract android.service.autofill.augmented.FillResponse.Builder addInlineAction(android.service.autofill.InlinePresentation)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
index de90b94..2a809b1 100644
--- a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
+++ b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -47,7 +46,6 @@
* CarrierMessagingService.
* @hide
*/
-@SystemApi
public abstract class CarrierMessagingServiceWrapper {
// Populated by bindToCarrierMessagingService. bindToCarrierMessagingService must complete
// prior to calling disposeConnection so that mCarrierMessagingServiceConnection is initialized.
@@ -64,7 +62,6 @@
* @return true upon successfully binding to a carrier messaging service, false otherwise
* @hide
*/
- @SystemApi
public boolean bindToCarrierMessagingService(@NonNull Context context,
@NonNull String carrierPackageName) {
Preconditions.checkState(mCarrierMessagingServiceConnection == null);
@@ -82,7 +79,6 @@
* @param context the context
* @hide
*/
- @SystemApi
public void disposeConnection(@NonNull Context context) {
Preconditions.checkNotNull(mCarrierMessagingServiceConnection);
context.unbindService(mCarrierMessagingServiceConnection);
@@ -93,7 +89,6 @@
* Implemented by subclasses to use the carrier messaging service once it is ready.
* @hide
*/
- @SystemApi
public abstract void onServiceReady();
/**
@@ -117,7 +112,6 @@
* @param callback the callback to notify upon completion
* @hide
*/
- @SystemApi
public void filterSms(@NonNull MessagePdu pdu, @NonNull String format, int destPort,
int subId, @NonNull final CarrierMessagingCallbackWrapper callback) {
if (mICarrierMessagingService != null) {
@@ -142,7 +136,6 @@
* @param callback the callback to notify upon completion
* @hide
*/
- @SystemApi
public void sendTextSms(@NonNull String text, int subId, @NonNull String destAddress,
int sendSmsFlag, @NonNull final CarrierMessagingCallbackWrapper callback) {
if (mICarrierMessagingService != null) {
@@ -168,7 +161,6 @@
* @param callback the callback to notify upon completion
* @hide
*/
- @SystemApi
public void sendDataSms(@NonNull byte[] data, int subId, @NonNull String destAddress,
int destPort, int sendSmsFlag,
@NonNull final CarrierMessagingCallbackWrapper callback) {
@@ -194,7 +186,6 @@
* @param callback the callback to notify upon completion
* @hide
*/
- @SystemApi
public void sendMultipartTextSms(@NonNull List<String> parts, int subId,
@NonNull String destAddress, int sendSmsFlag,
@NonNull final CarrierMessagingCallbackWrapper callback) {
@@ -220,7 +211,6 @@
* @param callback the callback to notify upon completion
* @hide
*/
- @SystemApi
public void sendMms(@NonNull Uri pduUri, int subId, @NonNull Uri location,
@NonNull final CarrierMessagingCallbackWrapper callback) {
if (mICarrierMessagingService != null) {
@@ -244,7 +234,6 @@
* @param callback the callback to notify upon completion
* @hide
*/
- @SystemApi
public void downloadMms(@NonNull Uri pduUri, int subId, @NonNull Uri location,
@NonNull final CarrierMessagingCallbackWrapper callback) {
if (mICarrierMessagingService != null) {
@@ -276,7 +265,6 @@
* {@link CarrierMessagingServiceWrapper}.
* @hide
*/
- @SystemApi
public abstract static class CarrierMessagingCallbackWrapper {
/**
@@ -289,7 +277,6 @@
* {@see CarrierMessagingService#onReceiveTextSms}.
* @hide
*/
- @SystemApi
public void onFilterComplete(int result) {
}
@@ -304,7 +291,6 @@
* only if result is {@link CarrierMessagingService#SEND_STATUS_OK}.
* @hide
*/
- @SystemApi
public void onSendSmsComplete(int result, int messageRef) {
}
@@ -319,7 +305,6 @@
* {@link CarrierMessagingService#SEND_STATUS_OK}.
* @hide
*/
- @SystemApi
public void onSendMultipartSmsComplete(int result, @Nullable int[] messageRefs) {
}
@@ -334,7 +319,6 @@
* {@link CarrierMessagingService#SEND_STATUS_OK}.
* @hide
*/
- @SystemApi
public void onSendMmsComplete(int result, @Nullable byte[] sendConfPdu) {
}
@@ -346,7 +330,6 @@
* and {@link CarrierMessagingService#SEND_STATUS_ERROR}.
* @hide
*/
- @SystemApi
public void onDownloadMmsComplete(int result) {
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index a88d389..7d070b1 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -227,7 +227,6 @@
@Nullable
private KeyphraseMetadata mKeyphraseMetadata;
private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
- private final IVoiceInteractionService mVoiceInteractionService;
private final IVoiceInteractionManagerService mModelManagementService;
private final SoundTriggerListener mInternalCallback;
private final Callback mExternalCallback;
@@ -413,14 +412,11 @@
* @param text The keyphrase text to get the detector for.
* @param locale The java locale for the detector.
* @param callback A non-null Callback for receiving the recognition events.
- * @param voiceInteractionService The current voice interaction service.
* @param modelManagementService A service that allows management of sound models.
- *
* @hide
*/
public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback,
KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
- IVoiceInteractionService voiceInteractionService,
IVoiceInteractionManagerService modelManagementService) {
mText = text;
mLocale = locale;
@@ -428,7 +424,6 @@
mExternalCallback = callback;
mHandler = new MyHandler();
mInternalCallback = new SoundTriggerListener(mHandler);
- mVoiceInteractionService = voiceInteractionService;
mModelManagementService = modelManagementService;
new RefreshAvailabiltyTask().execute();
}
@@ -471,6 +466,8 @@
/**
* Get the audio capabilities supported by the platform which can be enabled when
* starting a recognition.
+ * Caller must be the active voice interaction service via
+ * Settings.Secure.VOICE_INTERACTION_SERVICE.
*
* @see #AUDIO_CAPABILITY_ECHO_CANCELLATION
* @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION
@@ -488,7 +485,7 @@
private int getSupportedAudioCapabilitiesLocked() {
try {
ModuleProperties properties =
- mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
+ mModelManagementService.getDspModuleProperties();
if (properties != null) {
return properties.audioCapabilities;
}
@@ -501,6 +498,8 @@
/**
* Starts recognition for the associated keyphrase.
+ * Caller must be the active voice interaction service via
+ * Settings.Secure.VOICE_INTERACTION_SERVICE.
*
* @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
* @see #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
@@ -533,6 +532,8 @@
/**
* Stops recognition for the associated keyphrase.
+ * Caller must be the active voice interaction service via
+ * Settings.Secure.VOICE_INTERACTION_SERVICE.
*
* @return Indicates whether the call succeeded or not.
* @throws UnsupportedOperationException if the recognition isn't supported.
@@ -565,6 +566,8 @@
* stopping recognition. Once the model is unloaded, the value will be lost.
* {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this
* method.
+ * Caller must be the active voice interaction service via
+ * Settings.Secure.VOICE_INTERACTION_SERVICE.
*
* @param modelParam {@link ModelParams}
* @param value Value to set
@@ -595,6 +598,8 @@
* value is returned. See {@link ModelParams} for parameter default values.
* {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before
* calling this method.
+ * Caller must be the active voice interaction service via
+ * Settings.Secure.VOICE_INTERACTION_SERVICE.
*
* @param modelParam {@link ModelParams}
* @return value of parameter
@@ -617,6 +622,8 @@
* Determine if parameter control is supported for the given model handle.
* This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter}
* or {@link AlwaysOnHotwordDetector#getParameter}.
+ * Caller must be the active voice interaction service via
+ * Settings.Secure.VOICE_INTERACTION_SERVICE.
*
* @param modelParam {@link ModelParams}
* @return supported range of parameter, null if not supported
@@ -775,7 +782,7 @@
int code = STATUS_ERROR;
try {
- code = mModelManagementService.startRecognition(mVoiceInteractionService,
+ code = mModelManagementService.startRecognition(
mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
recognitionExtra, null /* additional data */, audioCapabilities));
@@ -791,8 +798,8 @@
private int stopRecognitionLocked() {
int code = STATUS_ERROR;
try {
- code = mModelManagementService.stopRecognition(
- mVoiceInteractionService, mKeyphraseMetadata.id, mInternalCallback);
+ code = mModelManagementService.stopRecognition(mKeyphraseMetadata.id,
+ mInternalCallback);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in stopRecognition!", e);
}
@@ -805,8 +812,8 @@
private int setParameterLocked(@ModelParams int modelParam, int value) {
try {
- int code = mModelManagementService.setParameter(mVoiceInteractionService,
- mKeyphraseMetadata.id, modelParam, value);
+ int code = mModelManagementService.setParameter(mKeyphraseMetadata.id, modelParam,
+ value);
if (code != STATUS_OK) {
Slog.w(TAG, "setParameter failed with error code " + code);
@@ -820,8 +827,7 @@
private int getParameterLocked(@ModelParams int modelParam) {
try {
- return mModelManagementService.getParameter(mVoiceInteractionService,
- mKeyphraseMetadata.id, modelParam);
+ return mModelManagementService.getParameter(mKeyphraseMetadata.id, modelParam);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -831,8 +837,7 @@
private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
try {
SoundTrigger.ModelParamRange modelParamRange =
- mModelManagementService.queryParameter(mVoiceInteractionService,
- mKeyphraseMetadata.id, modelParam);
+ mModelManagementService.queryParameter(mKeyphraseMetadata.id, modelParam);
if (modelParamRange == null) {
return null;
@@ -966,7 +971,7 @@
ModuleProperties dspModuleProperties = null;
try {
dspModuleProperties =
- mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
+ mModelManagementService.getDspModuleProperties();
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in getDspProperties!", e);
}
@@ -982,7 +987,7 @@
private void internalUpdateEnrolledKeyphraseMetadata() {
try {
mKeyphraseMetadata = mModelManagementService.getEnrolledKeyphraseMetadata(
- mVoiceInteractionService, mText, mLocale.toLanguageTag());
+ mText, mLocale.toLanguageTag());
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in internalUpdateEnrolledKeyphraseMetadata", e);
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fc99836..b54e4d9 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -199,7 +199,7 @@
throw new IllegalStateException("Not available until onReady() is called");
}
try {
- mSystemService.showSession(mInterface, args, flags);
+ mSystemService.showSession(args, flags);
} catch (RemoteException e) {
}
}
@@ -302,7 +302,7 @@
// Allow only one concurrent recognition via the APIs.
safelyShutdownHotwordDetector();
mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
- mKeyphraseEnrollmentInfo, mInterface, mSystemService);
+ mKeyphraseEnrollmentInfo, mSystemService);
}
return mHotwordDetector;
}
@@ -373,7 +373,7 @@
}
try {
- mSystemService.setUiHints(mInterface, hints);
+ mSystemService.setUiHints(hints);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 2c077bb..7238b12 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
@@ -62,7 +61,6 @@
*
* @hide
*/
-@SystemApi
@TestApi
public class TelephonyRegistryManager {
@@ -286,7 +284,6 @@
* @param incomingNumber incoming phone number.
* @hide
*/
- @SystemApi
@TestApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void notifyCallStateChangedForAllSubscriptions(@CallState int state,
@@ -302,7 +299,6 @@
* Notify {@link SubscriptionInfo} change.
* @hide
*/
- @SystemApi
public void notifySubscriptionInfoChanged() {
try {
sRegistry.notifySubscriptionInfoChanged();
@@ -315,7 +311,6 @@
* Notify opportunistic {@link SubscriptionInfo} change.
* @hide
*/
- @SystemApi
public void notifyOpportunisticSubscriptionInfoChanged() {
try {
sRegistry.notifyOpportunisticSubscriptionInfoChanged();
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index f226369..43afc15 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -17,7 +17,6 @@
package android.view;
import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.toPublicType;
import android.annotation.Nullable;
import android.inputmethodservice.InputMethodService;
@@ -99,6 +98,15 @@
}
}
+ @Override
+ void hide(boolean animationFinished) {
+ super.hide();
+ if (animationFinished) {
+ // remove IME surface as IME has finished hide animation.
+ removeSurface();
+ }
+ }
+
/**
* Request {@link InputMethodManager} to show the IME.
* @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
@@ -128,6 +136,11 @@
}
@Override
+ public void removeSurface() {
+ getImm().removeImeSurface();
+ }
+
+ @Override
public void setControl(@Nullable InsetsSourceControl control, int[] showTypes,
int[] hideTypes) {
super.setControl(control, showTypes, hideTypes);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4a6a5a0..65ea6bb 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -694,7 +694,7 @@
if (shown) {
showDirectly(controller.getTypes());
} else {
- hideDirectly(controller.getTypes());
+ hideDirectly(controller.getTypes(), true /* animationFinished */);
}
}
@@ -852,10 +852,10 @@
: LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
}
- private void hideDirectly(@InsetsType int types) {
+ private void hideDirectly(@InsetsType int types, boolean animationFinished) {
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
- getSourceConsumer(internalTypes.valueAt(i)).hide();
+ getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished);
}
}
@@ -887,7 +887,7 @@
if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
showDirectly(types);
} else {
- hideDirectly(types);
+ hideDirectly(types, false /* animationFinished */);
}
if (mViewRoot.mView == null) {
return;
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index e6497c0..e3a7de1 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -16,12 +16,10 @@
package android.view;
-import static android.view.InsetsController.ANIMATION_TYPE_NONE;
import static android.view.InsetsState.toPublicType;
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.util.MutableShort;
import android.view.InsetsState.InternalInsetsType;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
@@ -137,6 +135,10 @@
setRequestedVisible(false);
}
+ void hide(boolean animationFinished) {
+ hide();
+ }
+
/**
* Called when current window gains focus
*/
@@ -201,6 +203,13 @@
}
/**
+ * Remove surface on which this consumer type is drawn.
+ */
+ public void removeSurface() {
+ // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
+ }
+
+ /**
* Sets requested visibility from the client, regardless of whether we are able to control it at
* the moment.
*/
diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
index 024de4d..cb0320e 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
@@ -69,6 +69,9 @@
/** The type of the UI. */
private final @NonNull @Type String mType;
+ /** Whether the suggestion should be pinned or not. */
+ private final boolean mPinned;
+
/**
* Creates a new {@link InlineSuggestionInfo}, for testing purpose.
*
@@ -79,8 +82,8 @@
public static InlineSuggestionInfo newInlineSuggestionInfo(
@NonNull InlinePresentationSpec presentationSpec,
@NonNull @Source String source,
- @Nullable String[] autofillHints) {
- return new InlineSuggestionInfo(presentationSpec, source, autofillHints, TYPE_SUGGESTION);
+ @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned) {
+ return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned);
}
@@ -127,6 +130,8 @@
* Hints for the type of data being suggested.
* @param type
* The type of the UI.
+ * @param pinned
+ * Whether the suggestion should be pinned or not.
* @hide
*/
@DataClass.Generated.Member
@@ -134,7 +139,8 @@
@NonNull InlinePresentationSpec presentationSpec,
@NonNull @Source String source,
@Nullable String[] autofillHints,
- @NonNull @Type String type) {
+ @NonNull @Type String type,
+ boolean pinned) {
this.mPresentationSpec = presentationSpec;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPresentationSpec);
@@ -163,6 +169,7 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mType);
+ this.mPinned = pinned;
// onConstructed(); // You can define this method to get a callback
}
@@ -199,6 +206,14 @@
return mType;
}
+ /**
+ * Whether the suggestion should be pinned or not.
+ */
+ @DataClass.Generated.Member
+ public boolean isPinned() {
+ return mPinned;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -209,7 +224,8 @@
"presentationSpec = " + mPresentationSpec + ", " +
"source = " + mSource + ", " +
"autofillHints = " + java.util.Arrays.toString(mAutofillHints) + ", " +
- "type = " + mType +
+ "type = " + mType + ", " +
+ "pinned = " + mPinned +
" }";
}
@@ -229,7 +245,8 @@
&& java.util.Objects.equals(mPresentationSpec, that.mPresentationSpec)
&& java.util.Objects.equals(mSource, that.mSource)
&& java.util.Arrays.equals(mAutofillHints, that.mAutofillHints)
- && java.util.Objects.equals(mType, that.mType);
+ && java.util.Objects.equals(mType, that.mType)
+ && mPinned == that.mPinned;
}
@Override
@@ -243,6 +260,7 @@
_hash = 31 * _hash + java.util.Objects.hashCode(mSource);
_hash = 31 * _hash + java.util.Arrays.hashCode(mAutofillHints);
_hash = 31 * _hash + java.util.Objects.hashCode(mType);
+ _hash = 31 * _hash + Boolean.hashCode(mPinned);
return _hash;
}
@@ -253,6 +271,7 @@
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
+ if (mPinned) flg |= 0x10;
if (mAutofillHints != null) flg |= 0x4;
dest.writeByte(flg);
dest.writeTypedObject(mPresentationSpec, flags);
@@ -273,6 +292,7 @@
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
+ boolean pinned = (flg & 0x10) != 0;
InlinePresentationSpec presentationSpec = (InlinePresentationSpec) in.readTypedObject(InlinePresentationSpec.CREATOR);
String source = in.readString();
String[] autofillHints = (flg & 0x4) == 0 ? null : in.createStringArray();
@@ -306,6 +326,7 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mType);
+ this.mPinned = pinned;
// onConstructed(); // You can define this method to get a callback
}
@@ -325,10 +346,10 @@
};
@DataClass.Generated(
- time = 1579806757327L,
+ time = 1582753084046L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java",
- inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint({\"IntentName\"}) @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.view.inline.InlinePresentationSpec,java.lang.String,java.lang.String[])\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+ inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint({\"IntentName\"}) @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final boolean mPinned\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.view.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 71c9e33..869a929 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -348,6 +348,27 @@
* {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
* {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
* {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
+ * @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call
+ * of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated
+ * with this callback.
+ * @hide
+ */
+ @MainThread
+ public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+ IBinder hideInputToken);
+
+ /**
+ * Request that any soft input part of the input method be hidden from the user.
+ * @param flags Provides additional information about the show request.
+ * Currently always 0.
+ * @param resultReceiver The client requesting the show may wish to
+ * be told the impact of their request, which should be supplied here.
+ * The result code should be
+ * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+ * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN
+ * InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+ * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+ * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
*/
@MainThread
public void hideSoftInput(int flags, ResultReceiver resultReceiver);
@@ -366,4 +387,13 @@
* @hide
*/
public void setCurrentShowInputToken(IBinder showInputToken);
+
+ /**
+ * Update token of the client window requesting {@link #hideSoftInput(int, ResultReceiver)}
+ * @param hideInputToken dummy app window token for window requesting
+ * {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}
+ * @hide
+ */
+ public void setCurrentHideInputToken(IBinder hideInputToken);
+
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 39d5f5c..f3aa314 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1709,7 +1709,7 @@
}
try {
- return mService.hideSoftInput(mClient, flags, resultReceiver);
+ return mService.hideSoftInput(mClient, windowToken, flags, resultReceiver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1986,7 +1986,8 @@
@UnsupportedAppUsage
void closeCurrentInput() {
try {
- mService.hideSoftInput(mClient, HIDE_NOT_ALWAYS, null);
+ mService.hideSoftInput(
+ mClient, mCurRootView.getView().getWindowToken(), HIDE_NOT_ALWAYS, null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2056,6 +2057,21 @@
}
/**
+ * Notify IME directly to remove surface as it is no longer visible.
+ * @hide
+ */
+ public void removeImeSurface() {
+ synchronized (mH) {
+ try {
+ if (mCurMethod != null) {
+ mCurMethod.removeImeSurface();
+ }
+ } catch (RemoteException re) {
+ }
+ }
+ }
+
+ /**
* Report the current selection range.
*
* <p><strong>Editor authors</strong>, you need to call this method whenever
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index eb81628..0d688ff 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -191,4 +191,10 @@
* @hide
*/
public void notifyImeHidden();
+
+ /**
+ * Notify IME directly to remove surface as it is no longer visible.
+ * @hide
+ */
+ public void removeImeSurface();
}
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index a299b01..8ea824d 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -353,8 +353,9 @@
// Gets the startX for new style, which should be bounded by the horizontal bounds.
// Also calculates the left/right cut width for pixel copy.
- leftBound += mViewCoordinatesInSurface[0];
- rightBound += mViewCoordinatesInSurface[0];
+ leftBound = Math.max(leftBound + mViewCoordinatesInSurface[0], 0);
+ rightBound = Math.min(
+ rightBound + mViewCoordinatesInSurface[0], mContentCopySurface.mWidth);
mLeftCutWidth = Math.max(0, leftBound - startX);
mRightCutWidth = Math.max(0, startX + mSourceWidth - rightBound);
startX = Math.max(startX, leftBound);
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index dc6942c..6a0b443 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -71,8 +71,8 @@
void noteSyncStart(String name, int uid);
void noteSyncFinish(String name, int uid);
- void noteJobStart(String name, int uid, int standbyBucket, int jobid);
- void noteJobFinish(String name, int uid, int stopReason, int standbyBucket, int jobid);
+ void noteJobStart(String name, int uid);
+ void noteJobFinish(String name, int uid, int stopReason);
void noteStartWakelock(int uid, int pid, String name, String historyName,
int type, boolean unimportantForLogging);
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 417e23f..71ee8af 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -33,7 +33,7 @@
import android.service.voice.IVoiceInteractionSession;
interface IVoiceInteractionManagerService {
- void showSession(IVoiceInteractionService service, in Bundle sessionArgs, int flags);
+ void showSession(in Bundle sessionArgs, int flags);
boolean deliverNewSession(IBinder token, IVoiceInteractionSession session,
IVoiceInteractor interactor);
boolean showSessionFromSession(IBinder token, in Bundle sessionArgs, int flags);
@@ -52,68 +52,91 @@
/**
* Gets the registered Sound model for keyphrase detection for the current user.
* May be null if no matching sound model exists.
+ * Caller must either be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}, or the caller must be a voice model
+ * enrollment application detected by
+ * {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}.
*
* @param keyphraseId The unique identifier for the keyphrase.
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
+ * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
*/
@UnsupportedAppUsage
SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
/**
- * Add/Update the given keyphrase sound model.
+ * Add/Update the given keyphrase sound model for the current user.
+ * Caller must either be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}, or the caller must be a voice model
+ * enrollment application detected by
+ * {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}.
+ *
+ * @param model The keyphrase sound model to store peristantly.
+ * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
*/
int updateKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel model);
/**
* Deletes the given keyphrase sound model for the current user.
+ * Caller must either be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}, or the caller must be a voice model
+ * enrollment application detected by
+ * {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}.
*
* @param keyphraseId The unique identifier for the keyphrase.
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
+ * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
*/
int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
/**
* Gets the properties of the DSP hardware on this device, null if not present.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
*/
- SoundTrigger.ModuleProperties getDspModuleProperties(in IVoiceInteractionService service);
+ SoundTrigger.ModuleProperties getDspModuleProperties();
/**
* Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
* user ID of the caller.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
*
- * @param service The current VoiceInteractionService.
* @param keyphraseId The unique identifier for the keyphrase.
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
*/
- boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
- String bcp47Locale);
+ boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale);
/**
* Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale,
* and the user ID of the caller.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
*
- * @param service The current VoiceInteractionService
* @param keyphrase Keyphrase text associated with the enrolled model
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
* @return The metadata for the enrolled voice model bassed on the passed in parameters. Null if
* no matching voice model exists.
*/
- KeyphraseMetadata getEnrolledKeyphraseMetadata(IVoiceInteractionService service,
- String keyphrase, String bcp47Locale);
+ KeyphraseMetadata getEnrolledKeyphraseMetadata(String keyphrase, String bcp47Locale);
/**
* Starts a recognition for the given keyphrase.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
*/
- int startRecognition(in IVoiceInteractionService service, int keyphraseId,
- in String bcp47Locale, in IRecognitionStatusCallback callback,
+ int startRecognition(int keyphraseId, in String bcp47Locale,
+ in IRecognitionStatusCallback callback,
in SoundTrigger.RecognitionConfig recognitionConfig);
/**
* Stops a recognition for the given keyphrase.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
*/
- int stopRecognition(in IVoiceInteractionService service, int keyphraseId,
- in IRecognitionStatusCallback callback);
+ int stopRecognition(int keyphraseId, in IRecognitionStatusCallback callback);
/**
* Set a model specific ModelParams with the given value. This
* parameter will keep its value for the duration the model is loaded regardless of starting and
* stopping recognition. Once the model is unloaded, the value will be lost.
* queryParameter should be checked first before calling this method.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
*
- * @param service The current VoiceInteractionService.
* @param keyphraseId The unique identifier for the keyphrase.
* @param modelParam ModelParams
* @param value Value to set
@@ -123,36 +146,37 @@
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
* if API is not supported by HAL
*/
- int setParameter(in IVoiceInteractionService service, int keyphraseId,
- in ModelParams modelParam, int value);
+ int setParameter(int keyphraseId, in ModelParams modelParam, int value);
/**
* Get a model specific ModelParams. This parameter will keep its value
* for the duration the model is loaded regardless of starting and stopping recognition.
* Once the model is unloaded, the value will be lost. If the value is not set, a default
* value is returned. See ModelParams for parameter default values.
* queryParameter should be checked first before calling this method.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
*
- * @param service The current VoiceInteractionService.
* @param keyphraseId The unique identifier for the keyphrase.
* @param modelParam ModelParams
* @return value of parameter
*/
- int getParameter(in IVoiceInteractionService service, int keyphraseId,
- in ModelParams modelParam);
+ int getParameter(int keyphraseId, in ModelParams modelParam);
/**
* Determine if parameter control is supported for the given model handle.
* This method should be checked prior to calling setParameter or getParameter.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
*
- * @param service The current VoiceInteractionService.
* @param keyphraseId The unique identifier for the keyphrase.
* @param modelParam ModelParams
* @return supported range of parameter, null if not supported
*/
- @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service,
- int keyphraseId, in ModelParams modelParam);
+ @nullable SoundTrigger.ModelParamRange queryParameter(int keyphraseId,
+ in ModelParams modelParam);
/**
* @return the component name for the currently active voice interaction service
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
ComponentName getActiveServiceComponentName();
@@ -164,59 +188,70 @@
* @param sourceFlags flags indicating the source of this show
* @param showCallback optional callback to be notified when the session was shown
* @param activityToken optional token of activity that needs to be on top
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
boolean showSessionForActiveService(in Bundle args, int sourceFlags,
IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken);
/**
* Hides the session from the active service, if it is showing.
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
void hideCurrentSession();
/**
* Notifies the active service that a launch was requested from the Keyguard. This will only
* be called if {@link #activeServiceSupportsLaunchFromKeyguard()} returns true.
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
void launchVoiceAssistFromKeyguard();
/**
* Indicates whether there is a voice session running (but not necessarily showing).
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
boolean isSessionRunning();
/**
* Indicates whether the currently active voice interaction service is capable of handling the
* assist gesture.
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
boolean activeServiceSupportsAssist();
/**
* Indicates whether the currently active voice interaction service is capable of being launched
* from the lockscreen.
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
boolean activeServiceSupportsLaunchFromKeyguard();
/**
* Called when the lockscreen got shown.
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
void onLockscreenShown();
/**
* Register a voice interaction listener.
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
void registerVoiceInteractionSessionListener(IVoiceInteractionSessionListener listener);
/**
* Checks the availability of a set of voice actions for the current active voice service.
* Returns all supported voice actions.
+ * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
void getActiveServiceSupportedActions(in List<String> voiceActions,
in IVoiceActionCheckCallback callback);
/**
* Provide hints for showing UI.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
*/
- void setUiHints(in IVoiceInteractionService service, in Bundle hints);
+ void setUiHints(in Bundle hints);
/**
* Requests a list of supported actions from a specific activity.
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index ef9b3d10..73ef8c6 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.graphics.Point;
@@ -38,6 +39,7 @@
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsProvider;
import android.provider.MediaStore;
+import android.provider.MediaStore.Files.FileColumns;
import android.provider.MetadataReader;
import android.system.Int64Ref;
import android.text.TextUtils;
@@ -333,15 +335,17 @@
if (isDirectory) {
FileUtils.deleteContents(file);
}
- if (!file.delete()) {
+ // We could be deleting pending media which doesn't have any content yet, so only throw
+ // if the file exists and we fail to delete it.
+ if (file.exists() && !file.delete()) {
throw new IllegalStateException("Failed to delete " + file);
}
onDocIdChanged(docId);
- removeFromMediaStore(visibleFile, isDirectory);
+ removeFromMediaStore(visibleFile);
}
- private void removeFromMediaStore(@Nullable File visibleFile, boolean isFolder)
+ private void removeFromMediaStore(@Nullable File visibleFile)
throws FileNotFoundException {
// visibleFolder is null if we're removing a document from external thumb drive or SD card.
if (visibleFile != null) {
@@ -350,21 +354,19 @@
try {
final ContentResolver resolver = getContext().getContentResolver();
final Uri externalUri = MediaStore.Files.getContentUri("external");
+ final Bundle queryArgs = new Bundle();
+ queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_INCLUDE);
- // Remove media store entries for any files inside this directory, using
- // path prefix match. Logic borrowed from MtpDatabase.
- if (isFolder) {
- final String path = visibleFile.getAbsolutePath() + "/";
- resolver.delete(externalUri,
- "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
- new String[]{path + "%", Integer.toString(path.length()), path});
- }
-
- // Remove media store entry for this exact file.
- final String path = visibleFile.getAbsolutePath();
- resolver.delete(externalUri,
- "_data LIKE ?1 AND lower(_data)=lower(?2)",
- new String[]{path, path});
+ // Remove the media store entry corresponding to visibleFile and if it is a
+ // directory, also remove media store entries for any files inside this directory.
+ // Logic borrowed from com.android.providers.media.scan.ModernMediaScanner.
+ final String pathEscapedForLike = DatabaseUtils.escapeForLike(
+ visibleFile.getAbsolutePath());
+ ContentResolver.includeSqlSelectionArgs(queryArgs,
+ FileColumns.DATA + " LIKE ? ESCAPE '\\' OR "
+ + FileColumns.DATA + " LIKE ? ESCAPE '\\'",
+ new String[] {pathEscapedForLike + "/%", pathEscapedForLike});
+ resolver.delete(externalUri, queryArgs);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 20cd7c2..9a22686 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -41,5 +41,5 @@
boolean shouldOfferSwitchingToNextInputMethod();
void notifyUserAction();
void reportPreRendered(in EditorInfo info);
- void applyImeVisibility(IBinder showInputToken, boolean setVisible);
+ void applyImeVisibility(IBinder showOrHideInputToken, boolean setVisible);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 9eeef96..e5475f8 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -371,18 +371,20 @@
/**
* Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(IBinder, boolean)}.
*
- * @param showInputToken dummy token that maps to window requesting
- * {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)}
+ * @param showOrHideInputToken dummy token that maps to window requesting
+ * {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} or
+ * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow
+ * (IBinder, int)}
* @param setVisible {@code true} to set IME visible, else hidden.
*/
@AnyThread
- public void applyImeVisibility(IBinder showInputToken, boolean setVisible) {
+ public void applyImeVisibility(IBinder showOrHideInputToken, boolean setVisible) {
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
if (ops == null) {
return;
}
try {
- ops.applyImeVisibility(showInputToken, setVisible);
+ ops.applyImeVisibility(showOrHideInputToken, setVisible);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 75eb4aa..140c410 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -55,11 +55,8 @@
protected void saveLog(LogMaker log) {
// TODO(b/116684537): Flag guard logging to event log and statsd socket.
EventLogTags.writeSysuiMultiAction(log.serialize());
- if (log.getCategory() != MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER
- && log.getCategory() != MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) {
- FrameworkStatsLog.write(FrameworkStatsLog.KEY_VALUE_PAIRS_ATOM,
- /* UID is retrieved from statsd side */ 0, log.getEntries());
- }
+ FrameworkStatsLog.write(FrameworkStatsLog.KEY_VALUE_PAIRS_ATOM,
+ /* UID is retrieved from statsd side */ 0, log.getEntries());
}
public static final int VIEW_UNKNOWN = MetricsEvent.VIEW_UNKNOWN;
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index fd4b5ab..40e4f4d 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -55,7 +55,7 @@
void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver);
- void hideSoftInput(int flags, in ResultReceiver resultReceiver);
+ void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver);
void changeInputMethodSubtype(in InputMethodSubtype subtype);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 0337ddd..3f03f2a 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -43,7 +43,7 @@
boolean showSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
in ResultReceiver resultReceiver);
- boolean hideSoftInput(in IInputMethodClient client, int flags,
+ boolean hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
in ResultReceiver resultReceiver);
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'attribute' is non-null then also does startInput.
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index 664643c..0319e36 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -50,4 +50,6 @@
void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);
void notifyImeHidden();
+
+ void removeImeSurface();
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d27be27..ade2c7d 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -208,7 +208,6 @@
"libseccomp_policy",
"libgrallocusage",
"libscrypt_static",
- "libstatssocket",
],
shared_libs: [
@@ -266,6 +265,7 @@
"libdl",
"libdl_android",
"libstatslog",
+ "libstatssocket",
"libtimeinstate",
"server_configurable_flags",
"libstatspull",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index e4141e0..359fd48 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -626,12 +626,6 @@
// Set the jemalloc decay time to 1.
mallopt(M_DECAY_TIME, 1);
-
- // Maybe initialize GWP-ASan here. Must be called after
- // mallopt(M_SET_ZYGOTE_CHILD).
- bool ForceEnableGwpAsan = false;
- android_mallopt(M_INITIALIZE_GWP_ASAN, &ForceEnableGwpAsan,
- sizeof(ForceEnableGwpAsan));
}
static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
diff --git a/core/proto/android/app/job/enums.proto b/core/proto/android/app/job/enums.proto
index d2bf205..41863bb 100644
--- a/core/proto/android/app/job/enums.proto
+++ b/core/proto/android/app/job/enums.proto
@@ -34,5 +34,5 @@
STOP_REASON_TIMEOUT = 3;
STOP_REASON_DEVICE_IDLE = 4;
STOP_REASON_DEVICE_THERMAL = 5;
- STOP_REASON_RESTRAINED = 6;
+ STOP_REASON_RESTRICTED_BUCKET = 6;
}
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 6850d01..0455d58 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -225,9 +225,10 @@
message ScreenBrightnessSettingLimitsProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional int32 setting_minimum = 1;
- optional int32 setting_maximum = 2;
- optional int32 setting_default = 3;
+ reserved 1, 2, 3; // setting_minimum, setting_maximum, setting_default
+ optional float setting_minimum_float = 4;
+ optional float setting_maximum_float = 5;
+ optional float setting_default_float = 6;
}
// True to decouple auto-suspend mode from the display state.
diff --git a/data/etc/com.android.documentsui.xml b/data/etc/com.android.documentsui.xml
index 0ffaa21..b6671db 100644
--- a/data/etc/com.android.documentsui.xml
+++ b/data/etc/com.android.documentsui.xml
@@ -18,6 +18,8 @@
<privapp-permissions package="com.android.documentsui">
<permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
- <permission name="android.permission.MODIFY_QUIET_MODE"/>
+ <!-- Permissions required for reading and logging compat changes -->
+ <permission name="android.permission.LOG_COMPAT_CHANGE"/>
+ <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
</privapp-permissions>
</permissions>
diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java
index 3fa38e0..8ec8967 100644
--- a/media/java/android/media/voice/KeyphraseModelManager.java
+++ b/media/java/android/media/voice/KeyphraseModelManager.java
@@ -37,7 +37,8 @@
* manage voice based sound trigger models.
* Callers of this class are expected to have whitelist manifest permission MANAGE_VOICE_KEYPHRASES.
* Callers of this class are expected to be the designated voice interaction service via
- * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE} or a bundled voice model enrollment application
+ * detected by {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}.
* @hide
*/
@SystemApi
@@ -65,6 +66,10 @@
* {@link #updateKeyphraseSoundModel}.
* If the active voice interaction service changes from the current user, all requests will be
* rejected, and any registered models will be unregistered.
+ * Caller must either be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}, or the caller must be a voice model
+ * enrollment application detected by
+ * {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}.
*
* @param keyphraseId The unique identifier for the keyphrase.
* @param locale The locale language tag supported by the desired model.
@@ -93,6 +98,10 @@
* will be overwritten with the new model.
* If the active voice interaction service changes from the current user, all requests will be
* rejected, and any registered models will be unregistered.
+ * Caller must either be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}, or the caller must be a voice model
+ * enrollment application detected by
+ * {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}.
*
* @param model Keyphrase sound model to be updated.
* @throws ServiceSpecificException Thrown with error code if failed to update the keyphrase
@@ -120,6 +129,10 @@
* {@link #updateKeyphraseSoundModel}.
* If the active voice interaction service changes from the current user, all requests will be
* rejected, and any registered models will be unregistered.
+ * Caller must either be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}, or the caller must be a voice model
+ * enrollment application detected by
+ * {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}.
*
* @param keyphraseId The unique identifier for the keyphrase.
* @param locale The locale language tag supported by the desired model.
diff --git a/packages/SettingsLib/SchedulesProvider/res/values/config.xml b/packages/SettingsLib/SchedulesProvider/res/values/config.xml
new file mode 100644
index 0000000..48f3e3e
--- /dev/null
+++ b/packages/SettingsLib/SchedulesProvider/res/values/config.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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>
+ <!-- Package name for the caller of the Schedules provider. -->
+ <string name="config_schedules_provider_caller_package" translatable="false">com.android.settings</string>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
index 7d2b8e2..26bcd54 100644
--- a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
+++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java
@@ -24,9 +24,9 @@
import androidx.annotation.NonNull;
/**
- * This is a schedule data item. It contains the schedule title text, the summary text which
- * displays on the summary of the Settings preference and an {@link Intent}. Intent is able to
- * launch the editing page of the schedule data when user clicks this item (preference).
+ * Schedule data item containing the schedule title text, the summary text which is displayed on the
+ * summary of the Settings preference and an {@link Intent} which Settings will launch when the
+ * user clicks on the preference.
*/
public class ScheduleInfo implements Parcelable {
private static final String TAG = "ScheduleInfo";
@@ -40,7 +40,7 @@
mIntent = builder.mIntent;
}
- protected ScheduleInfo(Parcel in) {
+ private ScheduleInfo(Parcel in) {
mTitle = in.readString();
mSummary = in.readString();
mIntent = in.readParcelable(Intent.class.getClassLoader());
@@ -48,8 +48,6 @@
/**
* Returns the title text.
- *
- * @return The title.
*/
public String getTitle() {
return mTitle;
@@ -57,15 +55,14 @@
/**
* Returns the summary text.
- *
- * @return The summary.
*/
public String getSummary() {
return mSummary;
}
/**
- * Returns an {@link Intent}.
+ * Returns an {@link Intent} which Settings will launch when the user clicks on a schedule
+ * preference.
*/
public Intent getIntent() {
return mIntent;
@@ -107,19 +104,15 @@
@NonNull
@Override
public String toString() {
- return "title : " + mTitle + " summary : " + mSummary + (mIntent == null
- ? " and intent is null." : ".");
+ return "title: " + mTitle + ", summary: " + mSummary + ", intent: " + mIntent;
}
/**
* A simple builder for {@link ScheduleInfo}.
*/
public static class Builder {
- @NonNull
private String mTitle;
- @NonNull
private String mSummary;
- @NonNull
private Intent mIntent;
/**
diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
index a423e47..28d5f07 100644
--- a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
+++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java
@@ -21,19 +21,18 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
-import java.util.List;
import java.util.stream.Collectors;
/**
- * This provider is a bridge for client apps to provide the schedule data.
- * Client provider needs to implement their {@link #getScheduleInfoList()} and returns a list of
- * {@link ScheduleInfo}.
+ * A bridge for client apps to provide the schedule data. Client provider needs to implement
+ * {@link #getScheduleInfoList()} returning a list of {@link ScheduleInfo}.
*/
public abstract class SchedulesProvider extends ContentProvider {
public static final String METHOD_GENERATE_SCHEDULE_INFO_LIST = "generateScheduleInfoList";
@@ -46,9 +45,8 @@
}
@Override
- public final Cursor query(
- Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
+ public final Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
throw new UnsupportedOperationException("Query operation is not supported currently.");
}
@@ -74,18 +72,24 @@
}
/**
- * Return the list of the schedule information.
- *
- * @return a list of the {@link ScheduleInfo}.
+ * Returns the list of the schedule information.
*/
public abstract ArrayList<ScheduleInfo> getScheduleInfoList();
/**
- * Returns a bundle which contains a list of {@link ScheduleInfo} and data types:
- * scheduleInfoList : ArrayList<ScheduleInfo>
+ * Returns a bundle which contains a list of {@link ScheduleInfo}s:
+ *
+ * <ul>
+ * <li>scheduleInfoList: ArrayList<ScheduleInfo>
+ * </ul>
*/
@Override
public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+ if (!TextUtils.equals(getCallingPackage(),
+ getContext().getText(R.string.config_schedules_provider_caller_package))) {
+ return null;
+ }
+
final Bundle bundle = new Bundle();
if (METHOD_GENERATE_SCHEDULE_INFO_LIST.equals(method)) {
final ArrayList<ScheduleInfo> scheduleInfoList = filterInvalidData(
@@ -98,36 +102,40 @@
}
/**
- * To filter the invalid schedule info.
+ * Filters our invalid schedule infos from {@code schedulesInfoList}.
*
- * @param scheduleInfoList The list of the {@link ScheduleInfo}.
- * @return The valid list of the {@link ScheduleInfo}.
+ * @return valid {@link SchedulesInfo}s if {@code schedulesInfoList} is not null. Otherwise,
+ * null.
*/
- private ArrayList<ScheduleInfo> filterInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) {
+ @Nullable
+ private ArrayList<ScheduleInfo> filterInvalidData(
+ @Nullable ArrayList<ScheduleInfo> scheduleInfoList) {
if (scheduleInfoList == null) {
Log.d(TAG, "package : " + getContext().getPackageName() + " has no scheduling data.");
return null;
}
// Dump invalid data in debug mode.
if (SystemProperties.getInt("ro.debuggable", 0) == 1) {
- new Thread(() -> {
- dumpInvalidData(scheduleInfoList);
- }).start();
+ dumpInvalidData(scheduleInfoList);
}
- final List<ScheduleInfo> filteredList = scheduleInfoList
+ return scheduleInfoList
.stream()
- .filter(scheduleInfo -> scheduleInfo.isValid())
- .collect(Collectors.toList());
-
- return new ArrayList<>(filteredList);
+ .filter(ScheduleInfo::isValid)
+ .collect(Collectors.toCollection(ArrayList::new));
}
private void dumpInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) {
- Log.d(TAG, "package : " + getContext().getPackageName()
- + " provided some scheduling data are invalid.");
- scheduleInfoList
+ final boolean hasInvalidData = scheduleInfoList
.stream()
- .filter(scheduleInfo -> !scheduleInfo.isValid())
- .forEach(scheduleInfo -> Log.d(TAG, scheduleInfo.toString()));
+ .anyMatch(scheduleInfo -> !scheduleInfo.isValid());
+
+ if (hasInvalidData) {
+ Log.w(TAG, "package : " + getContext().getPackageName()
+ + " provided some scheduling data that are invalid.");
+ scheduleInfoList
+ .stream()
+ .filter(scheduleInfo -> !scheduleInfo.isValid())
+ .forEach(scheduleInfo -> Log.w(TAG, scheduleInfo.toString()));
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index f69e4f5..3ae9e1e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -15,9 +15,15 @@
*/
package com.android.settingslib.media;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_BLUETOOTH;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_UNKNOWN;
+import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_GROUP;
+import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
+import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
@@ -165,6 +171,26 @@
}
/**
+ * Release session to stop playing media on MediaDevice.
+ */
+ boolean releaseSession() {
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.w(TAG, "releaseSession() package name is null or empty!");
+ return false;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null) {
+ mRouterManager.getControllerForSession(info).release();
+ return true;
+ }
+
+ Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
+
+ return false;
+ }
+
+ /**
* Get the MediaDevice list that can be added to current media.
*
* @return list of MediaDevice
@@ -298,7 +324,9 @@
private void buildAllRoutes() {
for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
- addMediaDevice(route);
+ if (route.isSystemRoute()) {
+ addMediaDevice(route);
+ }
}
}
@@ -309,27 +337,29 @@
}
private void addMediaDevice(MediaRoute2Info route) {
- final int deviceType = route.getDeviceType();
+ final int deviceType = route.getType();
MediaDevice mediaDevice = null;
switch (deviceType) {
- case DEVICE_TYPE_UNKNOWN:
+ case TYPE_UNKNOWN:
+ case TYPE_REMOTE_TV:
+ case TYPE_REMOTE_SPEAKER:
+ case TYPE_GROUP:
//TODO(b/148765806): use correct device type once api is ready.
- final String defaultRoute = "DEFAULT_ROUTE";
- if (TextUtils.equals(defaultRoute, route.getOriginalId())) {
- mediaDevice =
- new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
- } else {
- mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
- mPackageName);
- if (!TextUtils.isEmpty(mPackageName)
- && TextUtils.equals(route.getClientPackageName(), mPackageName)) {
- mCurrentConnectedDevice = mediaDevice;
- }
+ mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
+ mPackageName);
+ if (!TextUtils.isEmpty(mPackageName)
+ && TextUtils.equals(route.getClientPackageName(), mPackageName)) {
+ mCurrentConnectedDevice = mediaDevice;
}
break;
- case DEVICE_TYPE_REMOTE_TV:
+ case TYPE_BUILTIN_SPEAKER:
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ mediaDevice =
+ new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
break;
- case DEVICE_TYPE_BLUETOOTH:
+ case TYPE_HEARING_AID:
+ case TYPE_BLUETOOTH_A2DP:
final BluetoothDevice device =
BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getOriginalId());
final CachedBluetoothDevice cachedDevice =
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 617da6e..c70811f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -282,6 +282,13 @@
}
/**
+ * Release session to stop playing media on MediaDevice.
+ */
+ public boolean releaseSession() {
+ return mInfoMediaManager.releaseSession();
+ }
+
+ /**
* Get the MediaDevice list that has been selected to current media.
*
* @return list of MediaDevice
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 9668629..edb121b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -96,6 +96,7 @@
final MediaRoute2Info info = mock(MediaRoute2Info.class);
when(info.getId()).thenReturn(TEST_ID);
when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(info.isSystemRoute()).thenReturn(true);
final List<MediaRoute2Info> routes = new ArrayList<>();
routes.add(info);
@@ -166,6 +167,7 @@
final MediaRoute2Info info = mock(MediaRoute2Info.class);
when(info.getId()).thenReturn(TEST_ID);
when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(info.isSystemRoute()).thenReturn(true);
final List<MediaRoute2Info> routes = new ArrayList<>();
routes.add(info);
@@ -221,6 +223,7 @@
final MediaRoute2Info info = mock(MediaRoute2Info.class);
when(info.getId()).thenReturn(TEST_ID);
when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(info.isSystemRoute()).thenReturn(true);
final List<MediaRoute2Info> routes = new ArrayList<>();
routes.add(info);
@@ -438,4 +441,23 @@
assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
}
+
+ @Test
+ public void releaseSession_packageNameIsNull_returnFalse() {
+ mInfoMediaManager.mPackageName = null;
+
+ assertThat(mInfoMediaManager.releaseSession()).isFalse();
+ }
+
+ @Test
+ public void releaseSession_removeSuccessfully_returnTrue() {
+ final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+ final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(info);
+
+ mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+ when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+ assertThat(mInfoMediaManager.releaseSession()).isTrue();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/ScheduleInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/ScheduleInfoTest.java
new file mode 100644
index 0000000..5ec89ed
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/ScheduleInfoTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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.settingslib.schedulesprovider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ScheduleInfoTest {
+ private static final String TEST_TITLE = "Night Light";
+ private static final String TEST_SUMMARY = "Night Light summary";
+ private static final String TEST_EMPTY_SUMMARY = "";
+
+ @Test
+ public void builder_usedValidArguments_isValid() {
+ final Intent intent = createTestIntent();
+ final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_SUMMARY, intent);
+
+ assertThat(info).isNotNull();
+ assertThat(info.isValid()).isTrue();
+ }
+
+ @Test
+ public void builder_useEmptySummary_isInvalid() {
+ final Intent intent = createTestIntent();
+ final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_EMPTY_SUMMARY, intent);
+
+ assertThat(info).isNotNull();
+ assertThat(info.isValid()).isFalse();
+ }
+
+ @Test
+ public void builder_intentIsNull_isInvalid() {
+ final ScheduleInfo info = new ScheduleInfo.Builder()
+ .setTitle(TEST_TITLE)
+ .setSummary(TEST_SUMMARY)
+ .build();
+
+ assertThat(info).isNotNull();
+ assertThat(info.isValid()).isFalse();
+ }
+
+ @Test
+ public void getTitle_setValidTitle_shouldReturnSameCorrectTitle() {
+ final Intent intent = createTestIntent();
+ final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_SUMMARY, intent);
+
+ assertThat(info.getTitle()).isEqualTo(TEST_TITLE);
+ }
+
+ @Test
+ public void getSummary_setValidSummary_shouldReturnSameCorrectSummary() {
+ final Intent intent = createTestIntent();
+ final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_SUMMARY, intent);
+
+ assertThat(info.getSummary()).isEqualTo(TEST_SUMMARY);
+ }
+
+ @Test
+ public void getIntent_setValidIntent_shouldReturnSameCorrectIntent() {
+ final Intent intent = createTestIntent();
+ final ScheduleInfo info = createTestScheduleInfo(TEST_TITLE, TEST_SUMMARY, intent);
+
+ assertThat(info.getIntent()).isEqualTo(intent);
+ }
+
+ private static Intent createTestIntent() {
+ return new Intent("android.settings.NIGHT_DISPLAY_SETTINGS").addCategory(
+ Intent.CATEGORY_DEFAULT);
+ }
+
+ private static ScheduleInfo createTestScheduleInfo(String title, String summary,
+ Intent intent) {
+ return new ScheduleInfo.Builder()
+ .setTitle(title)
+ .setSummary(summary)
+ .setIntent(intent)
+ .build();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/SchedulesProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/SchedulesProviderTest.java
new file mode 100644
index 0000000..eb2e8e0
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/schedulesprovider/SchedulesProviderTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 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.settingslib.schedulesprovider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+
+@RunWith(RobolectricTestRunner.class)
+public class SchedulesProviderTest {
+ private static final String INVALID_PACKAGE = "com.android.sunny";
+ private static final String VALID_PACKAGE = "com.android.settings";
+ private static final String INVALID_METHOD = "queryTestData";
+ private TestSchedulesProvider mProvider;
+
+ @Before
+ public void setUp() {
+ mProvider = Robolectric.setupContentProvider(TestSchedulesProvider.class);
+ shadowOf(mProvider).setCallingPackage(VALID_PACKAGE);
+ mProvider.setScheduleInfos(TestSchedulesProvider.createOneValidScheduleInfo());
+ }
+
+ @Test
+ public void call_invalidCallingPkg_returnNull() {
+ shadowOf(mProvider).setCallingPackage(INVALID_PACKAGE);
+
+ final Bundle bundle = mProvider.call(SchedulesProvider.METHOD_GENERATE_SCHEDULE_INFO_LIST,
+ null /* arg */, null /* extras */);
+
+ assertThat(bundle).isNull();
+ }
+
+ @Test
+ public void call_invalidMethod_returnBundleWithoutScheduleInfoData() {
+ final Bundle bundle = mProvider.call(INVALID_METHOD, null /* arg */, null /* extras */);
+
+ assertThat(bundle).isNotNull();
+ assertThat(bundle.containsKey(SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST)).isFalse();
+ }
+
+ @Test
+ public void call_validMethod_returnScheduleInfoData() {
+ final Bundle bundle = mProvider.call(SchedulesProvider.METHOD_GENERATE_SCHEDULE_INFO_LIST,
+ null /* arg */, null /* extras */);
+
+ assertThat(bundle).isNotNull();
+ assertThat(bundle.containsKey(SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST)).isTrue();
+ final ArrayList<ScheduleInfo> infos = bundle.getParcelableArrayList(
+ SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST);
+ assertThat(infos).hasSize(1);
+ }
+
+ @Test
+ public void call_addTwoValidData_returnScheduleInfoData() {
+ mProvider.setScheduleInfos(TestSchedulesProvider.createTwoValidScheduleInfos());
+ final Bundle bundle = mProvider.call(SchedulesProvider.METHOD_GENERATE_SCHEDULE_INFO_LIST,
+ null /* arg */, null /* extras */);
+
+ assertThat(bundle).isNotNull();
+ assertThat(bundle.containsKey(SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST)).isTrue();
+ final ArrayList<ScheduleInfo> infos = bundle.getParcelableArrayList(
+ SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST);
+ assertThat(infos).hasSize(2);
+ }
+
+ @Test
+ public void call_addTwoValidDataAndOneInvalidData_returnTwoScheduleInfoData() {
+ mProvider.setScheduleInfos(TestSchedulesProvider.createTwoValidAndOneInvalidScheduleInfo());
+ final Bundle bundle = mProvider.call(SchedulesProvider.METHOD_GENERATE_SCHEDULE_INFO_LIST,
+ null /* arg */, null /* extras */);
+
+ assertThat(bundle).isNotNull();
+ assertThat(bundle.containsKey(SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST)).isTrue();
+ final ArrayList<ScheduleInfo> infos = bundle.getParcelableArrayList(
+ SchedulesProvider.BUNDLE_SCHEDULE_INFO_LIST);
+ assertThat(infos).hasSize(2);
+ }
+
+ private static class TestSchedulesProvider extends SchedulesProvider {
+ private ArrayList<ScheduleInfo> mScheduleInfos = new ArrayList<>();
+
+ @Override
+ public ArrayList<ScheduleInfo> getScheduleInfoList() {
+ return mScheduleInfos;
+ }
+
+ void setScheduleInfos(ArrayList<ScheduleInfo> scheduleInfos) {
+ mScheduleInfos = scheduleInfos;
+ }
+
+ private static ArrayList<ScheduleInfo> createOneValidScheduleInfo() {
+ final ArrayList<ScheduleInfo> scheduleInfos = new ArrayList<>();
+ final Intent intent = new Intent("android.settings.NIGHT_DISPLAY_SETTINGS").addCategory(
+ Intent.CATEGORY_DEFAULT);
+ final ScheduleInfo info = new ScheduleInfo.Builder().setTitle(
+ "Night Light").setSummary("This a sunny test").setIntent(intent).build();
+ scheduleInfos.add(info);
+
+ return scheduleInfos;
+ }
+
+ private static ArrayList<ScheduleInfo> createTwoValidScheduleInfos() {
+ final ArrayList<ScheduleInfo> scheduleInfos = new ArrayList<>();
+ Intent intent = new Intent("android.settings.NIGHT_DISPLAY_SETTINGS").addCategory(
+ Intent.CATEGORY_DEFAULT);
+ ScheduleInfo info = new ScheduleInfo.Builder().setTitle(
+ "Night Light").setSummary("This a sunny test").setIntent(intent).build();
+ scheduleInfos.add(info);
+
+ intent = new Intent("android.settings.DISPLAY_SETTINGS").addCategory(
+ Intent.CATEGORY_DEFAULT);
+ info = new ScheduleInfo.Builder().setTitle("Display").setSummary(
+ "Display summary").setIntent(intent).build();
+ scheduleInfos.add(info);
+
+ return scheduleInfos;
+ }
+
+ private static ArrayList<ScheduleInfo> createTwoValidAndOneInvalidScheduleInfo() {
+ final ArrayList<ScheduleInfo> scheduleInfos = new ArrayList<>();
+ Intent intent = new Intent("android.settings.NIGHT_DISPLAY_SETTINGS").addCategory(
+ Intent.CATEGORY_DEFAULT);
+ ScheduleInfo info = new ScheduleInfo.Builder().setTitle(
+ "Night Light").setSummary("This a sunny test").setIntent(intent).build();
+ scheduleInfos.add(info);
+
+ intent = new Intent("android.settings.DISPLAY_SETTINGS").addCategory(
+ Intent.CATEGORY_DEFAULT);
+ info = new ScheduleInfo.Builder().setTitle("Display").setSummary(
+ "Display summary").setIntent(intent).build();
+ scheduleInfos.add(info);
+
+ intent = new Intent("android.settings.DISPLAY_SETTINGS").addCategory(
+ Intent.CATEGORY_DEFAULT);
+ info = new ScheduleInfo.Builder().setTitle("").setSummary("Display summary").setIntent(
+ intent).build();
+ scheduleInfos.add(info);
+
+ return scheduleInfos;
+ }
+ }
+}
diff --git a/packages/StatementService/AndroidManifest.xml b/packages/StatementService/AndroidManifest.xml
index d79d900..b00c37f 100644
--- a/packages/StatementService/AndroidManifest.xml
+++ b/packages/StatementService/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application
android:label="@string/service_name"
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 0b74a11a..a76f961 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -32,10 +32,10 @@
android:alpha="0"/>
<HorizontalScrollView
android:id="@+id/global_screenshot_actions_container"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="bottom|center"
- android:elevation="3dp"
+ android:layout_gravity="bottom|left"
+ android:elevation="1dp"
android:fillViewport="true"
android:layout_marginHorizontal="@dimen/screenshot_action_container_margin_horizontal"
android:gravity="center"
@@ -63,7 +63,7 @@
android:id="@+id/global_screenshot_dismiss_button"
android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
- android:elevation="9dp"
+ android:elevation="7dp"
android:visibility="gone">
<ImageView
android:layout_width="match_parent"
@@ -76,7 +76,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@android:color/white"
- android:elevation="8dp"
+ android:elevation="@dimen/screenshot_preview_elevation"
android:visibility="gone"/>
<com.android.systemui.screenshot.ScreenshotSelectorView
android:id="@+id/global_screenshot_selector"
diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
index 79867a16b..6b94bef 100644
--- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
@@ -19,7 +19,7 @@
android:id="@+id/global_screenshot_action_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="@dimen/screenshot_action_chip_margin_horizontal"
+ android:layout_marginRight="@dimen/screenshot_action_chip_margin_right"
android:layout_gravity="center"
android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
android:background="@drawable/action_chip_background"
@@ -35,7 +35,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:textSize="@dimen/screenshot_action_chip_text_size"
- android:textStyle="bold"
android:textColor="@color/global_screenshot_button_text"/>
</com.android.systemui.screenshot.ScreenshotActionChip>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9caaa9f..12b9254 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -305,7 +305,7 @@
<dimen name="global_screenshot_legacy_bg_padding">20dp</dimen>
<dimen name="global_screenshot_bg_padding">20dp</dimen>
<dimen name="global_screenshot_x_scale">80dp</dimen>
- <dimen name="screenshot_preview_elevation">8dp</dimen>
+ <dimen name="screenshot_preview_elevation">6dp</dimen>
<dimen name="screenshot_offset_y">48dp</dimen>
<dimen name="screenshot_offset_x">16dp</dimen>
<dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
@@ -314,13 +314,13 @@
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">10dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
- <dimen name="screenshot_action_container_padding_left">100dp</dimen>
+ <dimen name="screenshot_action_container_padding_left">96dp</dimen>
<dimen name="screenshot_action_container_padding_right">8dp</dimen>
<!-- Radius of the chip background on global screenshot actions -->
<dimen name="screenshot_button_corner_radius">20dp</dimen>
- <dimen name="screenshot_action_chip_margin_horizontal">4dp</dimen>
- <dimen name="screenshot_action_chip_padding_vertical">10dp</dimen>
- <dimen name="screenshot_action_chip_icon_size">20dp</dimen>
+ <dimen name="screenshot_action_chip_margin_right">8dp</dimen>
+ <dimen name="screenshot_action_chip_padding_vertical">7dp</dimen>
+ <dimen name="screenshot_action_chip_icon_size">18dp</dimen>
<dimen name="screenshot_action_chip_padding_start">8dp</dimen>
<!-- Padding between icon and text -->
<dimen name="screenshot_action_chip_padding_middle">8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 6ce6353..0f896c4 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -99,7 +99,8 @@
private static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
private DisplayManager mDisplayManager;
- private boolean mIsRegistered;
+ @VisibleForTesting
+ protected boolean mIsRegistered;
private final BroadcastDispatcher mBroadcastDispatcher;
private final Handler mMainHandler;
private final TunerService mTunerService;
@@ -168,7 +169,6 @@
mDisplayManager = mContext.getSystemService(DisplayManager.class);
updateRoundedCornerRadii();
setupDecorations();
-
mDisplayListener = new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
@@ -230,7 +230,10 @@
removeAllOverlays();
}
- if (hasOverlays() && !mIsRegistered) {
+ if (hasOverlays()) {
+ if (mIsRegistered) {
+ return;
+ }
DisplayMetrics metrics = new DisplayMetrics();
mDisplayManager.getDisplay(DEFAULT_DISPLAY).getMetrics(metrics);
mDensity = metrics.density;
@@ -271,7 +274,8 @@
return mContext.getDisplay().getCutout();
}
- private boolean hasOverlays() {
+ @VisibleForTesting
+ boolean hasOverlays() {
if (mOverlays == null) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 20b3386..2873811 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -73,9 +73,6 @@
private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";
- private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow";
- private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false;
-
/**
* When true, if a notification has the information necessary to bubble (i.e. valid
* contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
@@ -131,16 +128,6 @@
}
/**
- * When true, show a menu when a bubble is long-pressed, which will allow the user to take
- * actions on that bubble.
- */
- static boolean allowBubbleOverflow(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ALLOW_BUBBLE_OVERFLOW,
- ALLOW_BUBBLE_OVERFLOW_DEFAULT ? 1 : 0) != 0;
- }
-
- /**
* If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds
* {@link android.app.Notification.BubbleMetadata} to the notification entry as long as
* the notification has necessary info for BubbleMetadata.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 072c20c..df8e394 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -520,9 +520,6 @@
}
private void setUpOverflow() {
- if (!BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
- return;
- }
int overflowBtnIndex = 0;
if (mBubbleOverflow == null) {
mBubbleOverflow = new BubbleOverflow(mContext);
@@ -733,8 +730,7 @@
@Nullable
Bubble getExpandedBubble() {
if (mExpandedBubble == null
- || (BubbleExperimentConfig.allowBubbleOverflow(mContext)
- && mExpandedBubble.getIconView() == mBubbleOverflow.getBtn()
+ || (mExpandedBubble.getIconView() == mBubbleOverflow.getBtn()
&& mExpandedBubble.getKey() == BubbleOverflow.KEY)) {
return null;
}
@@ -785,9 +781,6 @@
}
private void updateOverflowBtnVisibility(boolean apply) {
- if (!BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
- return;
- }
if (mIsExpanded) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "Show overflow button.");
@@ -911,8 +904,7 @@
float y = event.getRawY();
if (mIsExpanded) {
if (isIntersecting(mBubbleContainer, x, y)) {
- if (BubbleExperimentConfig.allowBubbleOverflow(mContext)
- && isIntersecting(mBubbleOverflow.getBtn(), x, y)) {
+ if (isIntersecting(mBubbleOverflow.getBtn(), x, y)) {
return mBubbleOverflow.getBtn();
}
// Could be tapping or dragging a bubble while expanded
@@ -1645,11 +1637,8 @@
* @return the number of bubbles in the stack view.
*/
public int getBubbleCount() {
- if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
- // Subtract 1 for the overflow button which is always in the bubble container.
- return mBubbleContainer.getChildCount() - 1;
- }
- return mBubbleContainer.getChildCount();
+ // Subtract 1 for the overflow button that is always in the bubble container.
+ return mBubbleContainer.getChildCount() - 1;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d688f0a..c129035 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -96,6 +96,7 @@
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.InjectionInflationController;
import java.io.FileDescriptor;
@@ -378,6 +379,17 @@
private IKeyguardDrawnCallback mDrawnCallback;
private CharSequence mCustomMessage;
+ private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (properties.getKeyset().contains(NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN)) {
+ mShowHomeOverLockscreen = properties.getBoolean(
+ NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, true /* defaultValue */);
+ }
+ }
+ };
+
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
@@ -692,6 +704,8 @@
}
};
+ private DeviceConfigProxy mDeviceConfig;
+
/**
* Injected constructor. See {@link KeyguardModule}.
*/
@@ -705,7 +719,8 @@
DismissCallbackRegistry dismissCallbackRegistry,
KeyguardUpdateMonitor keyguardUpdateMonitor, DumpManager dumpManager,
@UiBackground Executor uiBgExecutor, PowerManager powerManager,
- TrustManager trustManager) {
+ TrustManager trustManager,
+ DeviceConfigProxy deviceConfig) {
super(context);
mFalsingManager = falsingManager;
mLockPatternUtils = lockPatternUtils;
@@ -718,20 +733,15 @@
mPM = powerManager;
mTrustManager = trustManager;
dumpManager.registerDumpable(getClass().getName(), this);
- mShowHomeOverLockscreen = DeviceConfig.getBoolean(
+ mDeviceConfig = deviceConfig;
+ mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
/* defaultValue = */ true);
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post,
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (properties.getKeyset().contains(NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN)) {
- mShowHomeOverLockscreen = properties.getBoolean(
- NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, true /* defaultValue */);
- }
- }
- });
+ mDeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ mHandler::post,
+ mOnPropertiesChangedListener);
}
public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index d7af360..367f464 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -31,6 +31,7 @@
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.util.DeviceConfigProxy;
import java.util.concurrent.Executor;
@@ -62,7 +63,8 @@
DumpManager dumpManager,
PowerManager powerManager,
TrustManager trustManager,
- @UiBackground Executor uiBgExecutor) {
+ @UiBackground Executor uiBgExecutor,
+ DeviceConfigProxy deviceConfig) {
return new KeyguardViewMediator(
context,
falsingManager,
@@ -75,6 +77,7 @@
dumpManager,
uiBgExecutor,
powerManager,
- trustManager);
+ trustManager,
+ deviceConfig);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index e98dec0..7b96268 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -48,6 +48,7 @@
import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.wm.DisplayChangeController;
import com.android.systemui.wm.DisplayController;
@@ -204,7 +205,8 @@
@Inject
public PipManager(Context context, BroadcastDispatcher broadcastDispatcher,
DisplayController displayController,
- FloatingContentCoordinator floatingContentCoordinator) {
+ FloatingContentCoordinator floatingContentCoordinator,
+ DeviceConfigProxy deviceConfig) {
mContext = context;
mActivityManager = ActivityManager.getService();
@@ -225,7 +227,7 @@
mInputConsumerController);
mTouchHandler = new PipTouchHandler(context, mActivityManager, activityTaskManager,
mMenuController, mInputConsumerController, mPipBoundsHandler, mPipTaskOrganizer,
- floatingContentCoordinator);
+ floatingContentCoordinator, deviceConfig);
mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
mTouchHandler.getMotionHelper());
displayController.addDisplayChangingController(mRotationController);
@@ -339,7 +341,10 @@
private void updateMovementBounds(Rect animatingBounds, boolean fromImeAdjustment,
boolean fromShelfAdjustment) {
- // Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+ mPipTaskOrganizer.onDisplayInfoChanged(mTmpDisplayInfo);
+ // Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler before
+ // passing to mTouchHandler, mTouchHandler would rely on the bounds calculated by
+ // mPipBoundsHandler with up-to-dated information
mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
animatingBounds, mTmpDisplayInfo);
mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 91f539c..980d18b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -411,11 +411,9 @@
}
private void adjustAndAnimatePipOffset(Rect originalBounds, int offset, int duration) {
- if (offset == 0) {
- return;
- }
SomeArgs args = SomeArgs.obtain();
args.arg1 = originalBounds;
+ // offset would be zero if triggered from screen rotation.
args.argi1 = offset;
args.argi2 = duration;
mHandler.sendMessage(mHandler.obtainMessage(MSG_OFFSET_ANIMATE, args));
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index 9fb6234..389793e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -41,6 +41,7 @@
import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.systemui.R;
import com.android.systemui.pip.PipBoundsHandler;
+import com.android.systemui.util.DeviceConfigProxy;
import java.util.concurrent.Executor;
@@ -77,7 +78,8 @@
private int mCtrlType;
public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
- PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper) {
+ PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper,
+ DeviceConfigProxy deviceConfig) {
final Resources res = context.getResources();
context.getDisplay().getMetrics(mDisplayMetrics);
mDisplayId = context.getDisplayId();
@@ -93,7 +95,7 @@
DeviceConfig.NAMESPACE_SYSTEMUI,
PIP_USER_RESIZE,
/* defaultValue = */ false);
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
+ deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
new DeviceConfig.OnPropertiesChangedListener() {
@Override
public void onPropertiesChanged(DeviceConfig.Properties properties) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 3b855db..3f73d01 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -48,6 +48,7 @@
import com.android.systemui.pip.PipTaskOrganizer;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.FloatingContentCoordinator;
import java.io.PrintWriter;
@@ -164,7 +165,8 @@
InputConsumerController inputConsumerController,
PipBoundsHandler pipBoundsHandler,
PipTaskOrganizer pipTaskOrganizer,
- FloatingContentCoordinator floatingContentCoordinator) {
+ FloatingContentCoordinator floatingContentCoordinator,
+ DeviceConfigProxy deviceConfig) {
// Initialize the Pip input consumer
mContext = context;
mActivityManager = activityManager;
@@ -179,7 +181,8 @@
mMotionHelper = new PipMotionHelper(mContext, activityTaskManager, pipTaskOrganizer,
mMenuController, mSnapAlgorithm, mFlingAnimationUtils, floatingContentCoordinator);
mPipResizeGestureHandler =
- new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper);
+ new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper,
+ deviceConfig);
mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
() -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
mMovementBounds, true /* allowMenuTimeout */, willResizeMenu()));
@@ -266,6 +269,10 @@
public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
+ final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
+ if (fromDisplayRotationChanged) {
+ mTouchState.reset();
+ }
// Re-calculate the expanded bounds
mNormalBounds = normalBounds;
@@ -293,7 +300,7 @@
// If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
// occluded by the IME or shelf.
- if (fromImeAdjustment || fromShelfAdjustment) {
+ if (fromImeAdjustment || fromShelfAdjustment || fromDisplayRotationChanged) {
if (mTouchState.isUserInteracting()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index f8db922..cdb2c53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -65,6 +65,7 @@
import com.android.systemui.statusbar.phone.ScrimState;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.DeviceConfigProxy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -183,7 +184,8 @@
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- @Main Executor mainExecutor) {
+ @Main Executor mainExecutor,
+ DeviceConfigProxy deviceConfig) {
mContext = context;
mMediaArtworkProcessor = mediaArtworkProcessor;
mKeyguardBypassController = keyguardBypassController;
@@ -221,7 +223,7 @@
DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
mContext.getMainExecutor(),
mPropertiesChangedListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 7e70c20..fe2f1f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -30,6 +30,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
+import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -59,11 +60,12 @@
private final Handler mHandler;
- //TODO: change this top <Entry, List<Entry>>?
- private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
- mTmpChildOrderMap = new HashMap<>();
+ /** Re-usable map of notifications to their sorted children.*/
+ private final HashMap<NotificationEntry, List<NotificationEntry>> mTmpChildOrderMap =
+ new HashMap<>();
// Dependencies:
+ private final DynamicChildBindController mDynamicChildBindController;
protected final NotificationLockscreenUserManager mLockscreenUserManager;
protected final NotificationGroupManager mGroupManager;
protected final VisualStabilityManager mVisualStabilityManager;
@@ -105,7 +107,8 @@
KeyguardBypassController bypassController,
BubbleController bubbleController,
DynamicPrivacyController privacyController,
- ForegroundServiceSectionController fgsSectionController) {
+ ForegroundServiceSectionController fgsSectionController,
+ DynamicChildBindController dynamicChildBindController) {
mContext = context;
mHandler = mainHandler;
mLockscreenUserManager = notificationLockscreenUserManager;
@@ -121,6 +124,7 @@
mBubbleController = bubbleController;
mDynamicPrivacyController = privacyController;
privacyController.addListener(this);
+ mDynamicChildBindController = dynamicChildBindController;
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -175,13 +179,12 @@
ent.getRow().setNeedsRedaction(needsRedaction);
if (mGroupManager.isChildInGroupWithSummary(ent.getSbn())) {
NotificationEntry summary = mGroupManager.getGroupSummary(ent.getSbn());
- List<ExpandableNotificationRow> orderedChildren =
- mTmpChildOrderMap.get(summary.getRow());
+ List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(summary);
if (orderedChildren == null) {
orderedChildren = new ArrayList<>();
- mTmpChildOrderMap.put(summary.getRow(), orderedChildren);
+ mTmpChildOrderMap.put(summary, orderedChildren);
}
- orderedChildren.add(ent.getRow());
+ orderedChildren.add(ent);
} else {
toShow.add(ent.getRow());
}
@@ -260,6 +263,7 @@
}
+ mDynamicChildBindController.updateChildContentViews(mTmpChildOrderMap);
mVisualStabilityManager.onReorderingFinished();
// clear the map again for the next usage
mTmpChildOrderMap.clear();
@@ -274,6 +278,7 @@
private void addNotificationChildrenAndSort() {
// Let's now add all notification children which are missing
boolean orderChanged = false;
+ ArrayList<ExpandableNotificationRow> orderedRows = new ArrayList<>();
for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
View view = mListContainer.getContainerChildAt(i);
if (!(view instanceof ExpandableNotificationRow)) {
@@ -283,11 +288,11 @@
ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
List<ExpandableNotificationRow> children = parent.getNotificationChildren();
- List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+ List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
childIndex++) {
- ExpandableNotificationRow childView = orderedChildren.get(childIndex);
+ ExpandableNotificationRow childView = orderedChildren.get(childIndex).getRow();
if (children == null || !children.contains(childView)) {
if (childView.getParent() != null) {
Log.wtf(TAG, "trying to add a notification child that already has " +
@@ -300,11 +305,13 @@
parent.addChildNotification(childView, childIndex);
mListContainer.notifyGroupChildAdded(childView);
}
+ orderedRows.add(childView);
}
// Finally after removing and adding has been performed we can apply the order.
- orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
+ orderChanged |= parent.applyChildOrder(orderedRows, mVisualStabilityManager,
mEntryManager);
+ orderedRows.clear();
}
if (orderChanged) {
mListContainer.generateChildOrderChangedEvent();
@@ -323,13 +330,13 @@
ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
List<ExpandableNotificationRow> children = parent.getNotificationChildren();
- List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
+ List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
if (children != null) {
toRemove.clear();
for (ExpandableNotificationRow childRow : children) {
if ((orderedChildren == null
- || !orderedChildren.contains(childRow))
+ || !orderedChildren.contains(childRow.getEntry()))
&& !childRow.keepInParent()) {
toRemove.add(childRow);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index cd5bb77..4c99a90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -42,6 +43,7 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.tracing.ProtoTracer;
+import com.android.systemui.util.DeviceConfigProxy;
import java.util.concurrent.Executor;
@@ -91,7 +93,8 @@
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- @Main Executor mainExecutor) {
+ @Main Executor mainExecutor,
+ DeviceConfigProxy deviceConfigProxy) {
return new NotificationMediaManager(
context,
statusBarLazy,
@@ -99,7 +102,8 @@
notificationEntryManager,
mediaArtworkProcessor,
keyguardBypassController,
- mainExecutor);
+ mainExecutor,
+ deviceConfigProxy);
}
/** */
@@ -135,7 +139,8 @@
KeyguardBypassController bypassController,
BubbleController bubbleController,
DynamicPrivacyController privacyController,
- ForegroundServiceSectionController fgsSectionController) {
+ ForegroundServiceSectionController fgsSectionController,
+ DynamicChildBindController dynamicChildBindController) {
return new NotificationViewHierarchyManager(
context,
mainHandler,
@@ -147,7 +152,8 @@
bypassController,
bubbleController,
privacyController,
- fgsSectionController);
+ fgsSectionController,
+ dynamicChildBindController);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
new file mode 100644
index 0000000..059d6ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 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.statusbar.notification;
+
+import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_EXPANDED;
+import static com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
+
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Controller that binds/unbinds views content views on notification group children.
+ *
+ * We currently only show a limited number of notification children even if more exist, so we
+ * can save memory by freeing content views when they're not visible and binding them again when
+ * they get close to being visible.
+ *
+ * Eventually, when {@link NotifPipeline} takes over as the new notification pipeline, we'll have
+ * more control over which notifications even make it to inflation in the first place and be able
+ * to enforce this at an earlier stage at the level of the {@link ExpandableNotificationRow}, but
+ * for now, we're just doing it at the level of content views.
+ */
+public class DynamicChildBindController {
+ private final RowContentBindStage mStage;
+ private final int mChildBindCutoff;
+
+ @Inject
+ public DynamicChildBindController(RowContentBindStage stage) {
+ this(stage, CHILD_BIND_CUTOFF);
+ }
+
+ /**
+ * @param childBindCutoff the cutoff where we no longer bother having content views bound
+ */
+ DynamicChildBindController(
+ RowContentBindStage stage,
+ int childBindCutoff) {
+ mStage = stage;
+ mChildBindCutoff = childBindCutoff;
+ }
+
+ /**
+ * Update the child content views, unbinding content views on children that won't be visible
+ * and binding content views on children that will be visible eventually.
+ *
+ * @param groupNotifs map of notification summaries to their children
+ */
+ public void updateChildContentViews(
+ Map<NotificationEntry, List<NotificationEntry>> groupNotifs) {
+ for (NotificationEntry entry : groupNotifs.keySet()) {
+ List<NotificationEntry> children = groupNotifs.get(entry);
+ for (int j = 0; j < children.size(); j++) {
+ NotificationEntry childEntry = children.get(j);
+ if (j >= mChildBindCutoff) {
+ if (hasChildContent(childEntry)) {
+ freeChildContent(childEntry);
+ }
+ } else {
+ if (!hasChildContent(childEntry)) {
+ bindChildContent(childEntry);
+ }
+ }
+ }
+ }
+ }
+
+ private boolean hasChildContent(NotificationEntry entry) {
+ ExpandableNotificationRow row = entry.getRow();
+ return row.getPrivateLayout().getContractedChild() != null
+ || row.getPrivateLayout().getExpandedChild() != null;
+ }
+
+ private void freeChildContent(NotificationEntry entry) {
+ RowContentBindParams params = mStage.getStageParams(entry);
+ params.freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
+ params.freeContentViews(FLAG_CONTENT_VIEW_EXPANDED);
+ mStage.requestRebind(entry, null);
+ }
+
+ private void bindChildContent(NotificationEntry entry) {
+ RowContentBindParams params = mStage.getStageParams(entry);
+ params.requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
+ params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
+ mStage.requestRebind(entry, null);
+ }
+
+ /**
+ * How big the buffer of extra views we keep around to be ready to show when we do need to
+ * dynamically inflate.
+ */
+ private static final int EXTRA_VIEW_BUFFER_COUNT = 1;
+
+ private static final int CHILD_BIND_CUTOFF =
+ NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED + EXTRA_VIEW_BUFFER_COUNT;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 004b56b..6d3f126 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -18,7 +18,10 @@
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -467,6 +470,14 @@
}
};
switch (inflationFlag) {
+ case FLAG_CONTENT_VIEW_CONTRACTED:
+ getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED,
+ freeViewRunnable);
+ break;
+ case FLAG_CONTENT_VIEW_EXPANDED:
+ getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED,
+ freeViewRunnable);
+ break;
case FLAG_CONTENT_VIEW_HEADS_UP:
getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
freeViewRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index e3ca283..6dd4ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -18,6 +18,7 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
import android.annotation.NonNull;
@@ -191,6 +192,18 @@
private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
@InflationFlag int inflateFlag) {
switch (inflateFlag) {
+ case FLAG_CONTENT_VIEW_CONTRACTED:
+ if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
+ row.getPrivateLayout().setContractedChild(null);
+ mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED);
+ }
+ break;
+ case FLAG_CONTENT_VIEW_EXPANDED:
+ if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_EXPANDED)) {
+ row.getPrivateLayout().setExpandedChild(null);
+ mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+ }
+ break;
case FLAG_CONTENT_VIEW_HEADS_UP:
if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
row.getPrivateLayout().setHeadsUpChild(null);
@@ -204,8 +217,6 @@
mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
}
break;
- case FLAG_CONTENT_VIEW_CONTRACTED:
- case FLAG_CONTENT_VIEW_EXPANDED:
default:
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index d1b9a87..27fd1b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -71,7 +71,13 @@
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
private static final int VISIBLE_TYPE_SINGLELINE = 3;
- public static final int UNDEFINED = -1;
+ /**
+ * Used when there is no content on the view such as when we're a public layout but don't
+ * need to show.
+ */
+ private static final int VISIBLE_TYPE_NONE = -1;
+
+ private static final int UNDEFINED = -1;
private final Rect mClipBounds = new Rect();
@@ -99,7 +105,7 @@
private HybridGroupManager mHybridGroupManager;
private int mClipTopAmount;
private int mContentHeight;
- private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
+ private int mVisibleType = VISIBLE_TYPE_NONE;
private boolean mAnimate;
private boolean mIsHeadsUp;
private boolean mLegacy;
@@ -141,7 +147,7 @@
/** The visible type at the start of a touch driven transformation */
private int mTransformationStartVisibleType;
/** The visible type at the start of an animation driven transformation */
- private int mAnimationStartVisibleType = UNDEFINED;
+ private int mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
private boolean mUserExpanding;
private int mSingleLineWidthIndention;
private boolean mForceSelectNextLayout = true;
@@ -386,7 +392,7 @@
mContractedChild = null;
mContractedWrapper = null;
if (mTransformationStartVisibleType == VISIBLE_TYPE_CONTRACTED) {
- mTransformationStartVisibleType = UNDEFINED;
+ mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
}
return;
}
@@ -434,7 +440,7 @@
mExpandedChild = null;
mExpandedWrapper = null;
if (mTransformationStartVisibleType == VISIBLE_TYPE_EXPANDED) {
- mTransformationStartVisibleType = UNDEFINED;
+ mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
}
if (mVisibleType == VISIBLE_TYPE_EXPANDED) {
selectLayout(false /* animate */, true /* force */);
@@ -472,7 +478,7 @@
mHeadsUpChild = null;
mHeadsUpWrapper = null;
if (mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP) {
- mTransformationStartVisibleType = UNDEFINED;
+ mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
}
if (mVisibleType == VISIBLE_TYPE_HEADSUP) {
selectLayout(false /* animate */, true /* force */);
@@ -597,7 +603,7 @@
}
// Size change of the expanded version
- if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart >= 0
+ if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart != UNDEFINED
&& mExpandedChild != null) {
return Math.min(mContentHeightAtAnimationStart, getViewHeight(VISIBLE_TYPE_EXPANDED));
}
@@ -607,10 +613,12 @@
hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
} else if (mExpandedChild != null) {
hint = getViewHeight(VISIBLE_TYPE_EXPANDED);
- } else {
+ } else if (mContractedChild != null) {
hint = getViewHeight(VISIBLE_TYPE_CONTRACTED)
+ mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_action_list_height);
+ } else {
+ hint = getMinHeight();
}
if (mExpandedChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_EXPANDED)) {
@@ -646,7 +654,7 @@
if (mForceSelectNextLayout) {
forceUpdateVisibilities();
}
- if (mTransformationStartVisibleType != UNDEFINED
+ if (mTransformationStartVisibleType != VISIBLE_TYPE_NONE
&& mVisibleType != mTransformationStartVisibleType
&& getViewForVisibleType(mTransformationStartVisibleType) != null) {
final TransformableView shownView = getTransformableViewForVisibleType(mVisibleType);
@@ -823,7 +831,7 @@
fireExpandedVisibleListenerIfVisible();
// forceUpdateVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
- mAnimationStartVisibleType = UNDEFINED;
+ mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
}
private void fireExpandedVisibleListenerIfVisible() {
@@ -898,7 +906,7 @@
fireExpandedVisibleListenerIfVisible();
// updateViewVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
- mAnimationStartVisibleType = UNDEFINED;
+ mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
}
private void updateViewVisibility(int visibleType, int type, View view,
@@ -924,7 +932,7 @@
if (hiddenView != getTransformableViewForVisibleType(mVisibleType)) {
hiddenView.setVisible(false);
}
- mAnimationStartVisibleType = UNDEFINED;
+ mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
}
});
fireExpandedVisibleListenerIfVisible();
@@ -1041,8 +1049,10 @@
&& (!mIsChildInGroup || isGroupExpanded()
|| !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) {
return VISIBLE_TYPE_CONTRACTED;
- } else {
+ } else if (!noExpandedChild) {
return VISIBLE_TYPE_EXPANDED;
+ } else {
+ return VISIBLE_TYPE_NONE;
}
}
}
@@ -1423,7 +1433,8 @@
if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
if ((!mIsHeadsUp && !mHeadsUpAnimatingAway)
|| mHeadsUpChild == null || !mContainingNotification.canShowHeadsUp()) {
- if (mExpandedChild.getHeight() <= mContractedChild.getHeight()) {
+ if (mContractedChild == null
+ || mExpandedChild.getHeight() <= mContractedChild.getHeight()) {
expandable = false;
}
} else if (mExpandedChild.getHeight() <= mHeadsUpChild.getHeight()) {
@@ -1514,7 +1525,7 @@
if (userExpanding) {
mTransformationStartVisibleType = mVisibleType;
} else {
- mTransformationStartVisibleType = UNDEFINED;
+ mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
mVisibleType = calculateVisibleType();
updateViewVisibilities(mVisibleType);
updateBackgroundColor(false);
@@ -1558,6 +1569,7 @@
}
public void setContentHeightAnimating(boolean animating) {
+ //TODO: It's odd that this does nothing when animating is true
if (!animating) {
mContentHeightAtAnimationStart = UNDEFINED;
}
@@ -1565,7 +1577,7 @@
@VisibleForTesting
boolean isAnimatingVisibleType() {
- return mAnimationStartVisibleType != UNDEFINED;
+ return mAnimationStartVisibleType != VISIBLE_TYPE_NONE;
}
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
@@ -1758,17 +1770,25 @@
}
public int getExpandHeight() {
- int viewType = VISIBLE_TYPE_EXPANDED;
- if (mExpandedChild == null) {
+ int viewType;
+ if (mExpandedChild != null) {
+ viewType = VISIBLE_TYPE_EXPANDED;
+ } else if (mContractedChild != null) {
viewType = VISIBLE_TYPE_CONTRACTED;
+ } else {
+ return getMinHeight();
}
return getViewHeight(viewType) + getExtraRemoteInputHeight(mExpandedRemoteInput);
}
public int getHeadsUpHeight(boolean forceNoHeader) {
- int viewType = VISIBLE_TYPE_HEADSUP;
- if (mHeadsUpChild == null) {
+ int viewType;
+ if (mHeadsUpChild != null) {
+ viewType = VISIBLE_TYPE_HEADSUP;
+ } else if (mContractedChild != null) {
viewType = VISIBLE_TYPE_CONTRACTED;
+ } else {
+ return getMinHeight();
}
// The headsUp remote input quickly switches to the expanded one, so lets also include that
// one
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 75ceb0f..d7c88e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -53,8 +53,7 @@
static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
@VisibleForTesting
static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
- @VisibleForTesting
- static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
+ public static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() {
private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 312c4ac..d29f4fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -31,14 +31,12 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.KeyChain;
-import android.security.KeyChain.KeyChainConnection;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -55,6 +53,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -85,7 +84,7 @@
private final DevicePolicyManager mDevicePolicyManager;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
- private final Handler mBgHandler;
+ private final Executor mBgExecutor;
@GuardedBy("mCallbacks")
private final ArrayList<SecurityControllerCallback> mCallbacks = new ArrayList<>();
@@ -101,16 +100,14 @@
/**
*/
@Inject
- public SecurityControllerImpl(Context context, @Background Handler bgHandler,
- BroadcastDispatcher broadcastDispatcher) {
- this(context, bgHandler, broadcastDispatcher, null);
- }
-
- public SecurityControllerImpl(Context context, Handler bgHandler,
- BroadcastDispatcher broadcastDispatcher, SecurityControllerCallback callback) {
+ public SecurityControllerImpl(
+ Context context,
+ @Background Handler bgHandler,
+ BroadcastDispatcher broadcastDispatcher,
+ @Background Executor bgExecutor
+ ) {
super(broadcastDispatcher);
mContext = context;
- mBgHandler = bgHandler;
mDevicePolicyManager = (DevicePolicyManager)
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
mConnectivityManager = (ConnectivityManager)
@@ -118,10 +115,8 @@
mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
mPackageManager = context.getPackageManager();
- mUserManager = (UserManager)
- context.getSystemService(Context.USER_SERVICE);
-
- addCallback(callback);
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mBgExecutor = bgExecutor;
IntentFilter filter = new IntentFilter();
filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
@@ -305,7 +300,23 @@
}
private void refreshCACerts(int userId) {
- new CACertLoader().execute(userId);
+ mBgExecutor.execute(() -> {
+ Pair<Integer, Boolean> idWithCert = null;
+ try (KeyChain.KeyChainConnection conn = KeyChain.bindAsUser(mContext,
+ UserHandle.of(userId))) {
+ boolean hasCACerts = !(conn.getService().getUserCaAliases().getList().isEmpty());
+ idWithCert = new Pair<Integer, Boolean>(userId, hasCACerts);
+ } catch (RemoteException | InterruptedException | AssertionError e) {
+ Log.i(TAG, "failed to get CA certs", e);
+ idWithCert = new Pair<Integer, Boolean>(userId, null);
+ } finally {
+ if (DEBUG) Log.d(TAG, "Refreshing CA Certs " + idWithCert);
+ if (idWithCert != null && idWithCert.second != null) {
+ mHasCACerts.put(idWithCert.first, idWithCert.second);
+ fireCallbacks();
+ }
+ }
+ });
}
private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
@@ -408,28 +419,4 @@
}
}
};
-
- protected class CACertLoader extends AsyncTask<Integer, Void, Pair<Integer, Boolean> > {
-
- @Override
- protected Pair<Integer, Boolean> doInBackground(Integer... userId) {
- try (KeyChainConnection conn = KeyChain.bindAsUser(mContext,
- UserHandle.of(userId[0]))) {
- boolean hasCACerts = !(conn.getService().getUserCaAliases().getList().isEmpty());
- return new Pair<Integer, Boolean>(userId[0], hasCACerts);
- } catch (RemoteException | InterruptedException | AssertionError e) {
- Log.i(TAG, "failed to get CA certs", e);
- return new Pair<Integer, Boolean>(userId[0], null);
- }
- }
-
- @Override
- protected void onPostExecute(Pair<Integer, Boolean> result) {
- if (DEBUG) Log.d(TAG, "onPostExecute " + result);
- if (result.second != null) {
- mHasCACerts.put(result.first, result.second);
- fireCallbacks();
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 86fe3008..311e8738 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -25,10 +25,10 @@
import android.util.KeyValueListParser;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.DeviceConfigProxy;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -62,10 +62,15 @@
private final Handler mHandler;
private final Context mContext;
+ private final DeviceConfigProxy mDeviceConfig;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@Inject
- public SmartReplyConstants(@Main Handler handler, Context context) {
+ public SmartReplyConstants(
+ @Main Handler handler,
+ Context context,
+ DeviceConfigProxy deviceConfig
+ ) {
mHandler = handler;
mContext = context;
final Resources resources = mContext.getResources();
@@ -86,31 +91,35 @@
mDefaultOnClickInitDelay = resources.getInteger(
R.integer.config_smart_replies_in_notifications_onclick_init_delay);
+ mDeviceConfig = deviceConfig;
registerDeviceConfigListener();
updateConstants();
}
private void registerDeviceConfigListener() {
- DeviceConfig.addOnPropertiesChangedListener(
+ mDeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_SYSTEMUI,
this::postToHandler,
- (properties) -> onDeviceConfigPropertiesChanged(properties.getNamespace()));
+ mOnPropertiesChangedListener);
}
private void postToHandler(Runnable r) {
this.mHandler.post(r);
}
- @VisibleForTesting
- void onDeviceConfigPropertiesChanged(String namespace) {
- if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(namespace)) {
- Log.e(TAG, "Received update from DeviceConfig for unrelated namespace: "
- + namespace);
- return;
- }
-
- updateConstants();
- }
+ private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())) {
+ Log.e(TAG,
+ "Received update from DeviceConfig for unrelated namespace: "
+ + properties.getNamespace());
+ return;
+ }
+ updateConstants();
+ }
+ };
private void updateConstants() {
synchronized (SmartReplyConstants.this) {
@@ -120,7 +129,7 @@
mRequiresTargetingP = readDeviceConfigBooleanOrDefaultIfEmpty(
SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P,
mDefaultRequiresP);
- mMaxSqueezeRemeasureAttempts = DeviceConfig.getInt(
+ mMaxSqueezeRemeasureAttempts = mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SSIN_MAX_SQUEEZE_REMEASURE_ATTEMPTS,
mDefaultMaxSqueezeRemeasureAttempts);
@@ -130,24 +139,24 @@
mShowInHeadsUp = readDeviceConfigBooleanOrDefaultIfEmpty(
SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP,
mDefaultShowInHeadsUp);
- mMinNumSystemGeneratedReplies = DeviceConfig.getInt(
+ mMinNumSystemGeneratedReplies = mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SSIN_MIN_NUM_SYSTEM_GENERATED_REPLIES,
mDefaultMinNumSystemGeneratedReplies);
- mMaxNumActions = DeviceConfig.getInt(
+ mMaxNumActions = mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS,
mDefaultMaxNumActions);
- mOnClickInitDelay = DeviceConfig.getInt(
+ mOnClickInitDelay = mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SSIN_ONCLICK_INIT_DELAY,
mDefaultOnClickInitDelay);
}
}
- private static boolean readDeviceConfigBooleanOrDefaultIfEmpty(String propertyName,
+ private boolean readDeviceConfigBooleanOrDefaultIfEmpty(String propertyName,
boolean defaultValue) {
- String value = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI, propertyName);
+ String value = mDeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI, propertyName);
if (TextUtils.isEmpty(value)) {
return defaultValue;
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index cfa2947..5f82118 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -126,9 +126,10 @@
/**
* Allow the media player to be shown in the QS area, controlled by 2 flags.
+ * On by default, but can be disabled by setting to 0
*/
public static boolean useQsMediaPlayer(Context context) {
- int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_player", 0);
+ int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_player", 1);
return flag > 0;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index a974c6d..1b34b3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -456,4 +456,52 @@
assertThat(rectsToRegion(Collections.singletonList(rect)).getBounds(), is(rect));
}
+ @Test
+ public void testRegistration_From_NoOverlay_To_HasOverlays() {
+ doReturn(false).when(mScreenDecorations).hasOverlays();
+ mScreenDecorations.start();
+ verify(mTunerService, times(0)).addTunable(any(), any());
+ verify(mTunerService, times(1)).removeTunable(any());
+ assertThat(mScreenDecorations.mIsRegistered, is(false));
+ reset(mTunerService);
+
+ doReturn(true).when(mScreenDecorations).hasOverlays();
+ mScreenDecorations.onConfigurationChanged(new Configuration());
+ verify(mTunerService, times(1)).addTunable(any(), any());
+ verify(mTunerService, times(0)).removeTunable(any());
+ assertThat(mScreenDecorations.mIsRegistered, is(true));
+ }
+
+ @Test
+ public void testRegistration_From_HasOverlays_To_HasOverlays() {
+ doReturn(true).when(mScreenDecorations).hasOverlays();
+
+ mScreenDecorations.start();
+ verify(mTunerService, times(1)).addTunable(any(), any());
+ verify(mTunerService, times(0)).removeTunable(any());
+ assertThat(mScreenDecorations.mIsRegistered, is(true));
+ reset(mTunerService);
+
+ mScreenDecorations.onConfigurationChanged(new Configuration());
+ verify(mTunerService, times(0)).addTunable(any(), any());
+ verify(mTunerService, times(0)).removeTunable(any());
+ assertThat(mScreenDecorations.mIsRegistered, is(true));
+ }
+
+ @Test
+ public void testRegistration_From_HasOverlays_To_NoOverlay() {
+ doReturn(true).when(mScreenDecorations).hasOverlays();
+
+ mScreenDecorations.start();
+ verify(mTunerService, times(1)).addTunable(any(), any());
+ verify(mTunerService, times(0)).removeTunable(any());
+ assertThat(mScreenDecorations.mIsRegistered, is(true));
+ reset(mTunerService);
+
+ doReturn(false).when(mScreenDecorations).hasOverlays();
+ mScreenDecorations.onConfigurationChanged(new Configuration());
+ verify(mTunerService, times(0)).addTunable(any(), any());
+ verify(mTunerService, times(1)).removeTunable(any());
+ assertThat(mScreenDecorations.mIsRegistered, is(false));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9ef5520..8320b05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -44,6 +44,8 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -69,6 +71,7 @@
private @Mock DumpManager mDumpManager;
private @Mock PowerManager mPowerManager;
private @Mock TrustManager mTrustManager;
+ private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private FalsingManagerFake mFalsingManager;
@@ -85,7 +88,7 @@
mContext, mFalsingManager, mLockPatternUtils, mBroadcastDispatcher,
mNotificationShadeWindowController, () -> mStatusBarKeyguardViewManager,
mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor,
- mPowerManager, mTrustManager);
+ mPowerManager, mTrustManager, mDeviceConfig);
mViewMediator.start();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 8e87e0a..cc5f149 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -41,11 +41,11 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -106,17 +106,14 @@
mock(KeyguardBypassController.class),
mock(BubbleController.class),
mock(DynamicPrivacyController.class),
- mock(ForegroundServiceSectionController.class));
+ mock(ForegroundServiceSectionController.class),
+ mock(DynamicChildBindController.class));
mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
}
private NotificationEntry createEntry() throws Exception {
ExpandableNotificationRow row = mHelper.createRow();
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(row.getEntry().getSbn())
- .build();
- entry.setRow(row);
- return entry;
+ return row.getEntry();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
new file mode 100644
index 0000000..bf2d598
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 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.statusbar.notification;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DynamicChildBindControllerTest extends SysuiTestCase {
+
+ private DynamicChildBindController mDynamicChildBindController;
+ private Map<NotificationEntry, List<NotificationEntry>> mGroupNotifs = new ArrayMap<>();
+ private static final int TEST_CHILD_BIND_CUTOFF = 5;
+
+ @Mock private RowContentBindStage mBindStage;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ allowTestableLooperAsMainThread();
+ when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
+ mDynamicChildBindController =
+ new DynamicChildBindController(mBindStage, TEST_CHILD_BIND_CUTOFF);
+ }
+
+ @Test
+ public void testContentViewsOfChildrenBeyondCutoffAreFreed() {
+ // GIVEN a group notification with one view beyond the cutoff with content bound
+ NotificationEntry summary = addGroup(TEST_CHILD_BIND_CUTOFF + 1);
+ NotificationEntry lastChild = mGroupNotifs.get(summary).get(TEST_CHILD_BIND_CUTOFF);
+
+ RowContentBindParams bindParams = mock(RowContentBindParams.class);
+ when(mBindStage.getStageParams(lastChild)).thenReturn(bindParams);
+
+ // WHEN the controller gets the list
+ mDynamicChildBindController.updateChildContentViews(mGroupNotifs);
+
+ // THEN we free content views
+ verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
+ verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_EXPANDED);
+ verify(mBindStage).requestRebind(eq(lastChild), any());
+ }
+
+ @Test
+ public void testContentViewsBeforeCutoffAreBound() {
+ // GIVEN a group notification with one view before the cutoff with content unbound
+ NotificationEntry summary = addGroup(TEST_CHILD_BIND_CUTOFF);
+ NotificationEntry lastChild = mGroupNotifs.get(summary).get(TEST_CHILD_BIND_CUTOFF - 1);
+
+ lastChild.getRow().getPrivateLayout().setContractedChild(null);
+ lastChild.getRow().getPrivateLayout().setExpandedChild(null);
+
+ RowContentBindParams bindParams = mock(RowContentBindParams.class);
+ when(mBindStage.getStageParams(lastChild)).thenReturn(bindParams);
+
+ // WHEN the controller gets the list
+ mDynamicChildBindController.updateChildContentViews(mGroupNotifs);
+
+ // THEN we bind content views
+ verify(bindParams).requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
+ verify(bindParams).requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
+ verify(mBindStage).requestRebind(eq(lastChild), any());
+ }
+
+ private NotificationEntry addGroup(int size) {
+ NotificationEntry summary = new NotificationEntryBuilder().build();
+ summary.setRow(createRow());
+ ArrayList<NotificationEntry> children = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ NotificationEntry child = new NotificationEntryBuilder().build();
+ child.setRow(createRow());
+ children.add(child);
+ }
+ mGroupNotifs.put(summary, children);
+ return summary;
+ }
+
+ private ExpandableNotificationRow createRow() {
+ ExpandableNotificationRow row = (ExpandableNotificationRow)
+ LayoutInflater.from(mContext).inflate(R.layout.status_bar_notification_row, null);
+ row.getPrivateLayout().setContractedChild(new View(mContext));
+ row.getPrivateLayout().setExpandedChild(new View(mContext));
+ return row;
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
index 69e4f22..18ea774 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
@@ -25,6 +25,7 @@
import android.media.MediaMetadata;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
@@ -66,6 +67,9 @@
allowTestableLooperAsMainThread();
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+
+ // These tests are for regular media style notifications, not controls in quick settings
+ Settings.System.putInt(mContext.getContentResolver(), "qs_media_player", 0);
}
private void makeTestNotification(long duration, boolean allowSeeking) throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index e6b0440..44184ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -28,6 +29,7 @@
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -37,7 +39,6 @@
import android.net.ConnectivityManager.NetworkCallback;
import android.net.NetworkRequest;
import android.os.Handler;
-import android.os.Looper;
import android.os.UserManager;
import android.security.IKeyChainService;
import android.test.suitebuilder.annotation.SmallTest;
@@ -46,34 +47,30 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class SecurityControllerTest extends SysuiTestCase implements SecurityControllerCallback {
+public class SecurityControllerTest extends SysuiTestCase {
private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
private final IKeyChainService.Stub mKeyChainService = mock(IKeyChainService.Stub.class);
private final UserManager mUserManager = mock(UserManager.class);
+ private final BroadcastDispatcher mBroadcastDispatcher = mock(BroadcastDispatcher.class);
+ private final Handler mHandler = mock(Handler.class);
private SecurityControllerImpl mSecurityController;
- private CountDownLatch mStateChangedLatch;
private ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class);
-
- // implementing SecurityControllerCallback
- @Override
- public void onStateChanged() {
- mStateChangedLatch.countDown();
- }
+ private FakeExecutor mBgExecutor;
+ private BroadcastReceiver mBroadcastReceiver;
@Before
public void setUp() throws Exception {
@@ -95,18 +92,23 @@
when(mKeyChainService.queryLocalInterface("android.security.IKeyChainService"))
.thenReturn(mKeyChainService);
- // Wait for callbacks from the onUserSwitched() function in the
- // constructor of mSecurityController
- mStateChangedLatch = new CountDownLatch(1);
- // TODO: Migrate this test to TestableLooper and use a handler attached
- // to that.
- mSecurityController = new SecurityControllerImpl(mContext,
- new Handler(Looper.getMainLooper()), mock(BroadcastDispatcher.class), this);
- }
+ ArgumentCaptor<BroadcastReceiver> brCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
- @After
- public void tearDown() {
- mSecurityController.removeCallback(this);
+ mBgExecutor = new FakeExecutor(new FakeSystemClock());
+ mSecurityController = new SecurityControllerImpl(
+ mContext,
+ mHandler,
+ mBroadcastDispatcher,
+ mBgExecutor);
+
+ verify(mBroadcastDispatcher).registerReceiverWithHandler(
+ brCaptor.capture(),
+ anyObject(),
+ anyObject(),
+ anyObject());
+
+ mBroadcastReceiver = brCaptor.getValue();
}
@Test
@@ -126,8 +128,6 @@
@Test
public void testWorkAccount() throws Exception {
- // Wait for the callbacks from setUp()
- assertTrue(mStateChangedLatch.await(1, TimeUnit.SECONDS));
assertFalse(mSecurityController.hasCACertInCurrentUser());
final int PRIMARY_USER_ID = 0;
@@ -140,53 +140,41 @@
assertTrue(mSecurityController.hasWorkProfile());
assertFalse(mSecurityController.hasCACertInWorkProfile());
- mStateChangedLatch = new CountDownLatch(1);
-
when(mKeyChainService.getUserCaAliases())
.thenReturn(new StringParceledListSlice(Arrays.asList("One CA Alias")));
- mSecurityController.new CACertLoader()
- .execute(MANAGED_USER_ID);
+ refreshCACerts(MANAGED_USER_ID);
+ mBgExecutor.runAllReady();
- assertTrue(mStateChangedLatch.await(3, TimeUnit.SECONDS));
assertTrue(mSecurityController.hasCACertInWorkProfile());
}
@Test
public void testCaCertLoader() throws Exception {
- // Wait for the callbacks from setUp()
- assertTrue(mStateChangedLatch.await(1, TimeUnit.SECONDS));
assertFalse(mSecurityController.hasCACertInCurrentUser());
// With a CA cert
- mStateChangedLatch = new CountDownLatch(1);
-
when(mKeyChainService.getUserCaAliases())
.thenReturn(new StringParceledListSlice(Arrays.asList("One CA Alias")));
- mSecurityController.new CACertLoader()
- .execute(0);
+ refreshCACerts(0);
+ mBgExecutor.runAllReady();
- assertTrue(mStateChangedLatch.await(3, TimeUnit.SECONDS));
assertTrue(mSecurityController.hasCACertInCurrentUser());
// Exception
- mStateChangedLatch = new CountDownLatch(1);
-
when(mKeyChainService.getUserCaAliases())
.thenThrow(new AssertionError("Test AssertionError"))
.thenReturn(new StringParceledListSlice(new ArrayList<String>()));
- mSecurityController.new CACertLoader()
- .execute(0);
+ refreshCACerts(0);
+ mBgExecutor.runAllReady();
- assertFalse(mStateChangedLatch.await(1, TimeUnit.SECONDS));
assertTrue(mSecurityController.hasCACertInCurrentUser());
- mSecurityController.new CACertLoader()
- .execute(0);
+ refreshCACerts(0);
+ mBgExecutor.runAllReady();
- assertTrue(mStateChangedLatch.await(1, TimeUnit.SECONDS));
assertFalse(mSecurityController.hasCACertInCurrentUser());
}
@@ -197,4 +185,13 @@
&& request.networkCapabilities.getCapabilities().length == 0
), any(NetworkCallback.class));
}
+
+ /**
+ * refresh CA certs by sending a user unlocked broadcast for the desired user
+ */
+ private void refreshCACerts(int userId) {
+ Intent intent = new Intent(Intent.ACTION_USER_UNLOCKED);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mBroadcastReceiver.onReceive(mContext, intent);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index c761a44..e4e0dc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -22,7 +22,6 @@
import android.app.RemoteInput;
import android.os.Handler;
-import android.os.Looper;
import android.provider.DeviceConfig;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -32,8 +31,8 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.DeviceConfigProxyFake;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,14 +41,13 @@
@TestableLooper.RunWithLooper
@SmallTest
public class SmartReplyConstantsTest extends SysuiTestCase {
-
- private static final int CONTENT_OBSERVER_TIMEOUT_SECONDS = 10;
-
private SmartReplyConstants mConstants;
+ private DeviceConfigProxyFake mDeviceConfig;
+ private TestableLooper mTestableLooper;
@Before
public void setUp() {
- resetAllDeviceConfigFlags();
+ mDeviceConfig = new DeviceConfigProxyFake();
TestableResources resources = mContext.getOrCreateTestableResources();
resources.addOverride(R.bool.config_smart_replies_in_notifications_enabled, true);
resources.addOverride(
@@ -62,12 +60,12 @@
2);
resources.addOverride(
R.integer.config_smart_replies_in_notifications_max_num_actions, -1);
- mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
- }
-
- @After
- public void tearDown() {
- resetAllDeviceConfigFlags();
+ mTestableLooper = TestableLooper.get(this);
+ mConstants = new SmartReplyConstants(
+ new Handler(mTestableLooper.getLooper()),
+ mContext,
+ mDeviceConfig
+ );
}
@Test
@@ -78,25 +76,21 @@
@Test
public void testIsEnabledWithInvalidConfig() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_ENABLED, "invalid config");
- triggerConstantsOnChange();
assertTrue(mConstants.isEnabled());
}
@Test
public void testIsEnabledWithValidConfig() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_ENABLED, "false");
- triggerConstantsOnChange();
assertFalse(mConstants.isEnabled());
}
@Test
public void testRequiresTargetingPConfig() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, "false");
- triggerConstantsOnChange();
assertEquals(false, mConstants.requiresTargetingP());
overrideSetting(SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, null);
- triggerConstantsOnChange();
assertEquals(true, mConstants.requiresTargetingP());
}
@@ -110,20 +104,17 @@
public void testGetMaxSqueezeRemeasureAttemptsWithInvalidConfig() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_MAX_SQUEEZE_REMEASURE_ATTEMPTS,
"invalid config");
- triggerConstantsOnChange();
assertEquals(7, mConstants.getMaxSqueezeRemeasureAttempts());
}
@Test
public void testGetMaxSqueezeRemeasureAttemptsWithValidConfig() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_MAX_SQUEEZE_REMEASURE_ATTEMPTS, "5");
- triggerConstantsOnChange();
assertEquals(5, mConstants.getMaxSqueezeRemeasureAttempts());
}
@Test
public void testGetEffectiveEditChoicesBeforeSendingWithNoConfig() {
- triggerConstantsOnChange();
assertFalse(
mConstants.getEffectiveEditChoicesBeforeSending(
RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
@@ -138,7 +129,6 @@
@Test
public void testGetEffectiveEditChoicesBeforeSendingWithEnabledConfig() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_EDIT_CHOICES_BEFORE_SENDING, "true");
- triggerConstantsOnChange();
assertTrue(
mConstants.getEffectiveEditChoicesBeforeSending(
RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
@@ -153,7 +143,6 @@
@Test
public void testGetEffectiveEditChoicesBeforeSendingWithDisabledConfig() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_EDIT_CHOICES_BEFORE_SENDING, "false");
- triggerConstantsOnChange();
assertFalse(
mConstants.getEffectiveEditChoicesBeforeSending(
RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
@@ -174,14 +163,12 @@
@Test
public void testShowInHeadsUpEnabled() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, "true");
- triggerConstantsOnChange();
assertTrue(mConstants.getShowInHeadsUp());
}
@Test
public void testShowInHeadsUpDisabled() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, "false");
- triggerConstantsOnChange();
assertFalse(mConstants.getShowInHeadsUp());
}
@@ -194,7 +181,6 @@
@Test
public void testGetMinNumSystemGeneratedRepliesWithValidConfig() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_MIN_NUM_SYSTEM_GENERATED_REPLIES, "5");
- triggerConstantsOnChange();
assertEquals(5, mConstants.getMinNumSystemGeneratedReplies());
}
@@ -207,7 +193,6 @@
@Test
public void testMaxNumActionsSet() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS, "10");
- triggerConstantsOnChange();
assertEquals(10, mConstants.getMaxNumActions());
}
@@ -219,38 +204,12 @@
@Test
public void testOnClickInitDelaySet() {
overrideSetting(SystemUiDeviceConfigFlags.SSIN_ONCLICK_INIT_DELAY, "50");
- triggerConstantsOnChange();
assertEquals(50, mConstants.getOnClickInitDelay());
}
private void overrideSetting(String propertyName, String value) {
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
propertyName, value, false /* makeDefault */);
- }
-
- private void triggerConstantsOnChange() {
- mConstants.onDeviceConfigPropertiesChanged(DeviceConfig.NAMESPACE_SYSTEMUI);
- }
-
- private void resetAllDeviceConfigFlags() {
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SSIN_ENABLED, null, false /* makeDefault */);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, null, false /* makeDefault */);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SSIN_MAX_SQUEEZE_REMEASURE_ATTEMPTS, null,
- false /* makeDefault */);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SSIN_EDIT_CHOICES_BEFORE_SENDING, null,
- false /* makeDefault */);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, null, false /* makeDefault */);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SSIN_MIN_NUM_SYSTEM_GENERATED_REPLIES, null,
- false /* makeDefault */);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS, null, false /* makeDefault */);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SSIN_ONCLICK_INIT_DELAY, null, false /* makeDefault */);
+ mTestableLooper.processAllMessages();
}
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 1eb7692..228c628 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -47,6 +47,7 @@
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.AndroidFuture;
@@ -243,20 +244,27 @@
}
mCallbacks.setLastResponse(sessionId);
+ final InlineSuggestionsResponse inlineSuggestionsResponse =
+ InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(
+ request, inlineSuggestionsData, focusedId, mContext,
+ dataset -> {
+ mCallbacks.logAugmentedAutofillSelected(sessionId,
+ dataset.getId());
+ try {
+ client.autofill(sessionId, dataset.getFieldIds(),
+ dataset.getFieldValues());
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Encounter exception autofilling the values");
+ }
+ }, onErrorCallback, remoteRenderService);
+
+ if (inlineSuggestionsResponse == null) {
+ Slog.w(TAG, "InlineSuggestionFactory created null response");
+ return;
+ }
+
try {
- inlineSuggestionsCallback.onInlineSuggestionsResponse(
- InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(
- request, inlineSuggestionsData, focusedId, mContext,
- dataset -> {
- mCallbacks.logAugmentedAutofillSelected(sessionId,
- dataset.getId());
- try {
- client.autofill(sessionId, dataset.getFieldIds(),
- dataset.getFieldValues());
- } catch (RemoteException e) {
- Slog.w(TAG, "Encounter exception autofilling the values");
- }
- }, onErrorCallback, remoteRenderService));
+ inlineSuggestionsCallback.onInlineSuggestionsResponse(inlineSuggestionsResponse);
} catch (RemoteException e) {
Slog.w(TAG, "Exception sending inline suggestions response back to IME.");
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 317ce4c..960997d 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2686,6 +2686,12 @@
requestHideFillUi(mCurrentViewId);
}
}, mService.getRemoteInlineSuggestionRenderServiceLocked());
+
+ if (inlineSuggestionsResponse == null) {
+ Slog.w(TAG, "InlineSuggestionFactory created null response");
+ return false;
+ }
+
try {
imeResponse.getCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse);
} catch (RemoteException e) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index fef49d4..0d1b6dd 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -150,13 +150,13 @@
final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
if (fieldIndex < 0) {
Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
- return null;
+ continue;
}
final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(
fieldIndex);
if (inlinePresentation == null) {
Slog.w(TAG, "InlinePresentation not found in dataset");
- return null;
+ continue;
}
if (!includeDataset(dataset, fieldIndex, filterText)) {
continue;
@@ -219,12 +219,12 @@
@Nullable RemoteInlineSuggestionRenderService remoteRenderService,
@NonNull Runnable onErrorCallback, @Nullable IBinder hostInputToken,
int displayId) {
- // TODO(b/146453195): fill in the autofill hint properly.
final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
inlinePresentation.getInlinePresentationSpec(),
isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
- : InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""},
- InlineSuggestionInfo.TYPE_ACTION);
+ : InlineSuggestionInfo.SOURCE_AUTOFILL,
+ inlinePresentation.getAutofillHints(),
+ InlineSuggestionInfo.TYPE_ACTION, inlinePresentation.isPinned());
final Runnable onClickAction = () -> {
Toast.makeText(context, "icon clicked", Toast.LENGTH_SHORT).show();
};
@@ -240,12 +240,12 @@
@NonNull RemoteInlineSuggestionRenderService remoteRenderService,
@NonNull Runnable onErrorCallback, @Nullable IBinder hostInputToken,
int displayId) {
- // TODO(b/146453195): fill in the autofill hint properly.
final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
inlinePresentation.getInlinePresentationSpec(),
isAugmented ? InlineSuggestionInfo.SOURCE_PLATFORM
- : InlineSuggestionInfo.SOURCE_AUTOFILL, new String[]{""},
- InlineSuggestionInfo.TYPE_SUGGESTION);
+ : InlineSuggestionInfo.SOURCE_AUTOFILL,
+ inlinePresentation.getAutofillHints(),
+ InlineSuggestionInfo.TYPE_SUGGESTION, inlinePresentation.isPinned());
final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
createInlineContentProvider(inlinePresentation,
@@ -262,7 +262,8 @@
@Nullable IBinder hostInputToken, int displayId) {
final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
inlinePresentation.getInlinePresentationSpec(),
- InlineSuggestionInfo.SOURCE_AUTOFILL, null, InlineSuggestionInfo.TYPE_SUGGESTION);
+ InlineSuggestionInfo.SOURCE_AUTOFILL, inlinePresentation.getAutofillHints(),
+ InlineSuggestionInfo.TYPE_SUGGESTION, inlinePresentation.isPinned());
return new InlineSuggestion(inlineSuggestionInfo,
createInlineContentProvider(inlinePresentation,
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 0f1a652..dd9cc64 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -183,7 +183,8 @@
static native boolean vibratorSupportsAmplitudeControl();
static native void vibratorSetAmplitude(int amplitude);
static native int[] vibratorGetSupportedEffects();
- static native long vibratorPerformEffect(long effect, long strength, Vibration vibration);
+ static native long vibratorPerformEffect(long effect, long strength, Vibration vibration,
+ boolean withCallback);
static native void vibratorPerformComposedEffect(
VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
static native boolean vibratorSupportsExternalControl();
@@ -1334,7 +1335,8 @@
// Input devices don't support prebaked effect, so skip trying it with them.
if (!usingInputDeviceVibrators) {
long duration = vibratorPerformEffect(prebaked.getId(),
- prebaked.getEffectStrength(), vib);
+ prebaked.getEffectStrength(), vib,
+ hasCapability(IVibrator.CAP_PERFORM_CALLBACK));
long timeout = duration;
if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) {
timeout *= ASYNC_TIMEOUT_MULTIPLIER;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index f408fe7..b7b52b1 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3501,8 +3501,11 @@
}
}
- // If unbound while waiting to start, remove the pending service
- mPendingServices.remove(s);
+ // If unbound while waiting to start and there is no connection left in this service,
+ // remove the pending service
+ if (s.getConnections().isEmpty()) {
+ mPendingServices.remove(s);
+ }
if ((c.flags&Context.BIND_AUTO_CREATE) != 0) {
boolean hasAutoCreate = s.hasAutoCreateConnections();
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 60aba27..cba6b92 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -708,7 +708,7 @@
void dumpHistoryProcessExitInfo(PrintWriter pw, String packageName) {
pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity exit-info)");
- SimpleDateFormat sdf = new SimpleDateFormat();
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
synchronized (mLock) {
pw.println("Last Timestamp of Persistence Into Persistent Storage: "
+ sdf.format(new Date(mLastAppExitInfoPersistTimestamp)));
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 119394f..dbad562 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -17,7 +17,6 @@
package com.android.server.am;
import android.app.ActivityManager;
-import android.app.job.JobProtoEnums;
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.content.ContentResolver;
import android.content.Context;
@@ -468,26 +467,18 @@
}
/** A scheduled job was started. */
- public void noteJobStart(String name, int uid, int standbyBucket, int jobid) {
+ public void noteJobStart(String name, int uid) {
enforceCallingPermission();
synchronized (mStats) {
mStats.noteJobStartLocked(name, uid);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
- uid, null, name,
- FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__STARTED,
- JobProtoEnums.STOP_REASON_UNKNOWN, standbyBucket, jobid);
}
}
/** A scheduled job was finished. */
- public void noteJobFinish(String name, int uid, int stopReason, int standbyBucket, int jobid) {
+ public void noteJobFinish(String name, int uid, int stopReason) {
enforceCallingPermission();
synchronized (mStats) {
mStats.noteJobFinishLocked(name, uid, stopReason);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
- uid, null, name,
- FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED, stopReason,
- standbyBucket, jobid);
}
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index eec68dc..be48374 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -911,10 +911,12 @@
pid = proc.pid;
name = proc.processName;
- if (proc.curAdj <= ProcessList.CACHED_APP_MIN_ADJ) {
+ if (proc.curAdj < ProcessList.CACHED_APP_MIN_ADJ
+ || proc.shouldNotFreeze) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Skipping freeze for process " + pid
- + " " + name + " (not cached)");
+ + " " + name + " curAdj = " + proc.curAdj
+ + ", shouldNotFreeze = " + proc.shouldNotFreeze);
}
return;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index c239feb1..3fd1b78 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1109,6 +1109,7 @@
app.adjTarget = null;
app.empty = false;
app.setCached(false);
+ app.shouldNotFreeze = false;
final int appUid = app.info.uid;
final int logUid = mService.mCurOomAdjUid;
@@ -1542,23 +1543,24 @@
}
boolean trackedProcState = false;
- if ((cr.flags& Context.BIND_WAIVE_PRIORITY) == 0) {
- ProcessRecord client = cr.binding.client;
- if (computeClients) {
- computeOomAdjLocked(client, cachedAdj, topApp, doingAll, now,
- cycleReEval, true);
- } else {
- client.setCurRawAdj(client.setAdj);
- client.setCurRawProcState(client.setProcState);
- }
+ ProcessRecord client = cr.binding.client;
+ if (computeClients) {
+ computeOomAdjLocked(client, cachedAdj, topApp, doingAll, now,
+ cycleReEval, true);
+ } else {
+ client.setCurRawAdj(client.setAdj);
+ client.setCurRawProcState(client.setProcState);
+ }
+
+ int clientAdj = client.getCurRawAdj();
+ int clientProcState = client.getCurRawProcState();
+
+ if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) {
if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
continue;
}
- int clientAdj = client.getCurRawAdj();
- int clientProcState = client.getCurRawProcState();
-
if (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE) {
procStateFromFGSClient = true;
}
@@ -1762,6 +1764,19 @@
+ ProcessList.makeProcStateString(procState));
}
}
+ } else { // BIND_WAIVE_PRIORITY == true
+ // BIND_WAIVE_PRIORITY bindings are special when it comes to the
+ // freezer. Processes bound via WPRI are expected to be running,
+ // but they are not promoted in the LRU list to keep them out of
+ // cached. As a result, they can freeze based on oom_adj alone.
+ // Normally, bindToDeath would fire when a cached app would die
+ // in the background, but nothing will fire when a running process
+ // pings a frozen process. Accordingly, any cached app that is
+ // bound by an unfrozen app via a WPRI binding has to remain
+ // unfrozen.
+ if (clientAdj < ProcessList.CACHED_APP_MIN_ADJ) {
+ app.shouldNotFreeze = true;
+ }
}
if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
app.treatLikeActivity = true;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index c2f03ec..f2ca1da 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -167,6 +167,7 @@
int lastCompactAction; // The most recent compaction action performed for this app.
boolean frozen; // True when the process is frozen.
long freezeUnfreezeTime; // Last time the app was (un)frozen, 0 for never
+ boolean shouldNotFreeze; // True if a process has a WPRI binding from an unfrozen process
private int mCurSchedGroup; // Currently desired scheduling class
int setSchedGroup; // Last set to background scheduling class
int trimMemoryLevel; // Last selected memory trimming level
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 7f7c9c4..441d9d9 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -16,6 +16,7 @@
package com.android.server.compat;
+import android.app.compat.ChangeIdStateCache;
import android.compat.Compatibility.ChangeConfig;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -80,6 +81,7 @@
void addChange(CompatChange change) {
synchronized (mChanges) {
mChanges.put(change.getId(), change);
+ invalidateCache();
}
}
@@ -172,6 +174,7 @@
addChange(c);
}
c.addPackageOverride(packageName, enabled);
+ invalidateCache();
}
return alreadyKnown;
}
@@ -228,6 +231,7 @@
// Should never occur, since validator is in the same process.
throw new RuntimeException("Unable to call override validator!", e);
}
+ invalidateCache();
}
return overrideExists;
}
@@ -250,6 +254,7 @@
addOverride(changeId, packageName, false);
}
+ invalidateCache();
}
}
@@ -279,6 +284,7 @@
throw new RuntimeException("Unable to call override validator!", e);
}
}
+ invalidateCache();
}
}
@@ -377,6 +383,7 @@
config.initConfigFromLib(Environment.buildPath(
apex.apexDirectory, "etc", "compatconfig"));
}
+ config.invalidateCache();
return config;
}
@@ -406,4 +413,8 @@
IOverrideValidator getOverrideValidator() {
return mOverrideValidator;
}
+
+ private void invalidateCache() {
+ ChangeIdStateCache.invalidate();
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index e3c545c..dcd0a78 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -846,6 +846,14 @@
private final WeakHashMap<IBinder, IBinder> mShowRequestWindowMap = new WeakHashMap<>();
/**
+ * Map of generated token to windowToken that is requesting
+ * {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}.
+ * This map tracks origin of hideSoftInput requests.
+ */
+ @GuardedBy("mMethodMap")
+ private final WeakHashMap<IBinder, IBinder> mHideRequestWindowMap = new WeakHashMap<>();
+
+ /**
* A ring buffer to store the history of {@link StartInputInfo}.
*/
private static final class StartInputHistory {
@@ -1064,7 +1072,7 @@
== AccessibilityService.SHOW_MODE_HIDDEN;
if (mAccessibilityRequestingNoSoftKeyboard) {
final boolean showRequested = mShowRequested;
- hideCurrentInputLocked(0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, 0, null,
SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
mShowRequested = showRequested;
} else if (mShowRequested) {
@@ -1695,7 +1703,9 @@
// TODO: Is it really possible that switchUserLocked() happens before system ready?
if (mSystemReady) {
- hideCurrentInputLocked(0, null, SoftInputShowHideReason.HIDE_SWITCH_USER);
+ hideCurrentInputLocked(
+ mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SWITCH_USER);
+
resetCurrentMethodAndClient(UnbindReason.SWITCH_USER);
buildInputMethodListLocked(initialUserSwitch);
if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
@@ -3040,7 +3050,7 @@
}
@Override
- public boolean hideSoftInput(IInputMethodClient client, int flags,
+ public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
ResultReceiver resultReceiver) {
int uid = Binder.getCallingUid();
synchronized (mMethodMap) {
@@ -3068,7 +3078,7 @@
}
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
- return hideCurrentInputLocked(flags, resultReceiver,
+ return hideCurrentInputLocked(windowToken, flags, resultReceiver,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -3076,7 +3086,7 @@
}
}
- boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver,
+ boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mShowExplicitlyRequested || mShowForced)) {
@@ -3100,12 +3110,14 @@
(mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
boolean res;
if (shouldHideSoftInput) {
+ final Binder hideInputToken = new Binder();
+ mHideRequestWindowMap.put(hideInputToken, windowToken);
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
// the final state.
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_HIDE_SOFT_INPUT,
- reason, mCurMethod, resultReceiver));
+ executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO(MSG_HIDE_SOFT_INPUT,
+ reason, mCurMethod, resultReceiver, hideInputToken));
res = true;
} else {
res = false;
@@ -3242,7 +3254,8 @@
Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ " a background user, use EditorInfo.targetInputMethodUser with"
+ " INTERACT_ACROSS_USERS_FULL permission.");
- hideCurrentInputLocked(0, null, SoftInputShowHideReason.HIDE_INVALID_USER);
+ hideCurrentInputLocked(
+ mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_INVALID_USER);
return InputBindResult.INVALID_USER;
}
@@ -3305,7 +3318,8 @@
// be behind any soft input window, so hide the
// soft input window if it is shown.
if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
- hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null,
+ hideCurrentInputLocked(
+ mCurFocusedWindow, InputMethodManager.HIDE_NOT_ALWAYS, null,
SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
// If focused display changed, we should unbind current method
@@ -3342,13 +3356,13 @@
case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
- hideCurrentInputLocked(0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, 0, null,
SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
}
break;
case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
if (DEBUG) Slog.v(TAG, "Window asks to hide input");
- hideCurrentInputLocked(0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, 0, null,
SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
break;
case LayoutParams.SOFT_INPUT_STATE_VISIBLE:
@@ -3832,7 +3846,7 @@
// Send it to window manager to hide IME from IME target window.
// TODO(b/139861270): send to mCurClient.client once IMMS is aware of
// actual IME target.
- mWindowManagerInternal.hideIme(mCurClient.selfReportedDisplayId);
+ mWindowManagerInternal.hideIme(mHideRequestWindowMap.get(windowToken));
}
} else {
// Send to window manager to show IME after IME layout finishes.
@@ -3872,7 +3886,10 @@
}
long ident = Binder.clearCallingIdentity();
try {
- hideCurrentInputLocked(flags, null, SoftInputShowHideReason.HIDE_MY_SOFT_INPUT);
+ hideCurrentInputLocked(
+ mLastImeTargetWindow, flags, null,
+ SoftInputShowHideReason.HIDE_MY_SOFT_INPUT);
+
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3969,11 +3986,11 @@
args.recycle();
return true;
case MSG_SHOW_SOFT_INPUT:
- args = (SomeArgs)msg.obj;
+ args = (SomeArgs) msg.obj;
try {
final @SoftInputShowHideReason int reason = msg.arg2;
if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
- + msg.arg1 + ", " + args.arg2 + ") for reason: "
+ + args.arg3 + ", " + msg.arg1 + ", " + args.arg2 + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
((IInputMethod) args.arg1).showSoftInput(
(IBinder) args.arg3, msg.arg1, (ResultReceiver) args.arg2);
@@ -3986,13 +4003,14 @@
args.recycle();
return true;
case MSG_HIDE_SOFT_INPUT:
- args = (SomeArgs)msg.obj;
+ args = (SomeArgs) msg.obj;
try {
final @SoftInputShowHideReason int reason = msg.arg1;
if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
- + args.arg2 + ") for reason: "
+ + args.arg3 + ", " + args.arg2 + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
- ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
+ ((IInputMethod)args.arg1).hideSoftInput(
+ (IBinder) args.arg3, 0, (ResultReceiver)args.arg2);
mSoftInputShowHideHistory.addEntry(
new SoftInputShowHideHistory.Entry(mCurClient,
InputMethodDebug.objToString(mCurFocusedWindow),
@@ -4004,7 +4022,8 @@
case MSG_HIDE_CURRENT_INPUT_METHOD:
synchronized (mMethodMap) {
final @SoftInputShowHideReason int reason = (int) msg.obj;
- hideCurrentInputLocked(0, null, reason);
+ hideCurrentInputLocked(mCurFocusedWindow, 0, null, reason);
+
}
return true;
case MSG_INITIALIZE_IME:
@@ -5409,7 +5428,7 @@
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
if (userId == mSettings.getCurrentUserId()) {
- hideCurrentInputLocked(0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, 0, null,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
unbindCurrentMethodLocked();
// Reset the current IME
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 1aff23b0..e60b910 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1500,7 +1500,8 @@
@BinderThread
@Override
public boolean hideSoftInput(
- IInputMethodClient client, int flags, ResultReceiver resultReceiver) {
+ IInputMethodClient client, IBinder windowToken, int flags,
+ ResultReceiver resultReceiver) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int userId = UserHandle.getUserId(callingUid);
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index fd8e159..b4ec359 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -79,8 +79,10 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/** Implementation of {@link AppIntegrityManagerService}. */
public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
@@ -101,12 +103,14 @@
private static final String TAG = "AppIntegrityManagerServiceImpl";
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
- private static final String PACKAGE_INSTALLER = "com.google.android.packageinstaller";
private static final String BASE_APK_FILE = "base.apk";
private static final String ALLOWED_INSTALLERS_METADATA_NAME = "allowed-installers";
private static final String ALLOWED_INSTALLER_DELIMITER = ",";
private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
+ private static final Set<String> PACKAGE_INSTALLER = new HashSet<>(
+ Arrays.asList("com.google.android.packageinstaller", "com.android.packageinstaller"));
+
// Access to files inside mRulesDir is protected by mRulesLock;
private final Context mContext;
private final Handler mHandler;
@@ -114,8 +118,6 @@
private final RuleEvaluationEngine mEvaluationEngine;
private final IntegrityFileManager mIntegrityFileManager;
- private final boolean mCheckIntegrityForRuleProviders;
-
/** Create an instance of {@link AppIntegrityManagerServiceImpl}. */
public static AppIntegrityManagerServiceImpl create(Context context) {
HandlerThread handlerThread = new HandlerThread("AppIntegrityManagerServiceHandler");
@@ -126,13 +128,7 @@
LocalServices.getService(PackageManagerInternal.class),
RuleEvaluationEngine.getRuleEvaluationEngine(),
IntegrityFileManager.getInstance(),
- handlerThread.getThreadHandler(),
- Settings.Global.getInt(
- context.getContentResolver(),
- Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
- 0)
- == 1
- );
+ handlerThread.getThreadHandler());
}
@VisibleForTesting
@@ -141,14 +137,12 @@
PackageManagerInternal packageManagerInternal,
RuleEvaluationEngine evaluationEngine,
IntegrityFileManager integrityFileManager,
- Handler handler,
- boolean checkIntegrityForRuleProviders) {
+ Handler handler) {
mContext = context;
mPackageManagerInternal = packageManagerInternal;
mEvaluationEngine = evaluationEngine;
mIntegrityFileManager = integrityFileManager;
mHandler = handler;
- mCheckIntegrityForRuleProviders = checkIntegrityForRuleProviders;
IntentFilter integrityVerificationFilter = new IntentFilter();
integrityVerificationFilter.addAction(ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
@@ -259,7 +253,7 @@
String installerPackageName = getInstallerPackageName(intent);
// Skip integrity verification if the verifier is doing the install.
- if (!mCheckIntegrityForRuleProviders
+ if (!integrityCheckIncludesRuleProvider()
&& isRuleProvider(installerPackageName)) {
Slog.i(TAG, "Verifier doing the install. Skipping integrity check.");
mPackageManagerInternal.setIntegrityVerificationResult(
@@ -271,8 +265,6 @@
List<String> installerCertificates =
getInstallerCertificateFingerprint(installerPackageName);
- Slog.w(TAG, appCertificates.toString());
-
AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();
builder.setPackageName(getPackageNameNormalized(packageName));
@@ -376,7 +368,7 @@
// A common way for apps to install packages is to send an intent to PackageInstaller. In
// that case, the installer will always show up as PackageInstaller which is not what we
// want.
- if (installer.equals(PACKAGE_INSTALLER)) {
+ if (PACKAGE_INSTALLER.contains(installer)) {
int originatingUid = intent.getIntExtra(EXTRA_ORIGINATING_UID, -1);
if (originatingUid < 0) {
Slog.e(TAG, "Installer is package installer but originating UID not found.");
@@ -631,4 +623,12 @@
return getAllowedRuleProviders().stream()
.anyMatch(ruleProvider -> ruleProvider.equals(installerPackageName));
}
+
+ private boolean integrityCheckIncludesRuleProvider() {
+ return Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
+ 0)
+ == 1;
+ }
}
diff --git a/services/core/java/com/android/server/location/GnssConfiguration.java b/services/core/java/com/android/server/location/GnssConfiguration.java
index b3546dc..a3523f2 100644
--- a/services/core/java/com/android/server/location/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/GnssConfiguration.java
@@ -80,7 +80,7 @@
// Represents an HAL interface version. Instances of this class are created in the JNI layer
// and returned through native methods.
- private static class HalInterfaceVersion {
+ static class HalInterfaceVersion {
final int mMajor;
final int mMinor;
@@ -206,6 +206,10 @@
native_set_satellite_blacklist(constellations, svids);
}
+ HalInterfaceVersion getHalInterfaceVersion() {
+ return native_get_gnss_configuration_version();
+ }
+
interface SetCarrierProperty {
boolean set(int value);
}
@@ -232,8 +236,7 @@
logConfigurations();
- final HalInterfaceVersion gnssConfigurationIfaceVersion =
- native_get_gnss_configuration_version();
+ final HalInterfaceVersion gnssConfigurationIfaceVersion = getHalInterfaceVersion();
if (gnssConfigurationIfaceVersion != null) {
// Set to a range checked value.
if (isConfigEsExtensionSecSupported(gnssConfigurationIfaceVersion)
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 5c2bf26..58e332a 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -807,10 +807,15 @@
locationRequest.setProvider(provider);
- // Ignore location settings if in emergency mode.
- if (isUserEmergency && mNIHandler.getInEmergency()) {
- locationRequest.setLocationSettingsIgnored(true);
- durationMillis *= EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER;
+ // Ignore location settings if in emergency mode. This is only allowed for
+ // isUserEmergency request (introduced in HAL v2.0), or DBH request in HAL v1.1.
+ if (mNIHandler.getInEmergency()) {
+ GnssConfiguration.HalInterfaceVersion halVersion =
+ mGnssConfiguration.getHalInterfaceVersion();
+ if (isUserEmergency || (halVersion.mMajor < 2 && !independentFromGnss)) {
+ locationRequest.setLocationSettingsIgnored(true);
+ durationMillis *= EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER;
+ }
}
Log.i(TAG,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f714af03..74cb93d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11780,14 +11780,22 @@
final String pkgName = pkg.getPackageName();
if (mCustomResolverComponentName != null &&
mCustomResolverComponentName.getPackageName().equals(pkg.getPackageName())) {
- setUpCustomResolverActivity(pkg);
+ setUpCustomResolverActivity(pkg, pkgSetting);
}
if (pkg.getPackageName().equals("android")) {
synchronized (mLock) {
// Set up information for our fall-back user intent resolution activity.
mPlatformPackage = pkg;
+
+ // The instance stored in PackageManagerService is special cased to be non-user
+ // specific, so initialize all the needed fields here.
mAndroidApplication = pkg.toAppInfoWithoutState();
+ mAndroidApplication.flags = PackageInfoUtils.appInfoFlags(pkg, pkgSetting);
+ mAndroidApplication.privateFlags =
+ PackageInfoUtils.appInfoPrivateFlags(pkg, pkgSetting);
+ mAndroidApplication.initForUser(UserHandle.USER_SYSTEM);
+
if (!mResolverReplaced) {
mResolveActivity.applicationInfo = mAndroidApplication;
mResolveActivity.name = ResolverActivity.class.getName();
@@ -11949,11 +11957,20 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- private void setUpCustomResolverActivity(AndroidPackage pkg) {
+ private void setUpCustomResolverActivity(AndroidPackage pkg, PackageSetting pkgSetting) {
synchronized (mLock) {
mResolverReplaced = true;
+
+ // The instance created in PackageManagerService is special cased to be non-user
+ // specific, so initialize all the needed fields here.
+ ApplicationInfo appInfo = pkg.toAppInfoWithoutState();
+ appInfo.flags = PackageInfoUtils.appInfoFlags(pkg, pkgSetting);
+ appInfo.privateFlags =
+ PackageInfoUtils.appInfoPrivateFlags(pkg, pkgSetting);
+ appInfo.initForUser(UserHandle.USER_SYSTEM);
+
// Set up information for custom user intent resolution activity.
- mResolveActivity.applicationInfo = pkg.toAppInfoWithoutState();
+ mResolveActivity.applicationInfo = appInfo;
mResolveActivity.name = mCustomResolverComponentName.getClassName();
mResolveActivity.packageName = pkg.getPackageName();
mResolveActivity.processName = pkg.getProcessName();
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 2453318..fa1da27 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -33,6 +33,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.compat.ChangeIdStateCache;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -441,6 +442,7 @@
private static void invalidatePackageCache() {
PackageManager.invalidatePackageInfoCache();
+ ChangeIdStateCache.invalidate();
}
PackageSetting getPackageLPr(String pkgName) {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 48dd9e6..2feddb6 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -733,7 +733,7 @@
if (!TextUtils.isEmpty(contentCapturePackageName)) {
grantPermissionsToSystemPackage(contentCapturePackageName, userId,
PHONE_PERMISSIONS, SMS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
- CONTACTS_PERMISSIONS);
+ CONTACTS_PERMISSIONS, STORAGE_PERMISSIONS);
}
// Atthention Service
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index f647b6a..4a85027 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -4471,6 +4471,9 @@
@Override
public void setCheckPermissionDelegate(CheckPermissionDelegate delegate) {
synchronized (mLock) {
+ if (delegate != null || mCheckPermissionDelegate != null) {
+ PackageManager.invalidatePackageInfoCache();
+ }
mCheckPermissionDelegate = delegate;
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8483c77..e6c23b6 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -4213,15 +4213,15 @@
.SCREEN_BRIGHTNESS_SETTING_LIMITS);
proto.write(
PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
- .SETTING_MINIMUM,
+ .SETTING_MINIMUM_FLOAT,
mScreenBrightnessSettingMinimum);
proto.write(
PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
- .SETTING_MAXIMUM,
+ .SETTING_MAXIMUM_FLOAT,
mScreenBrightnessSettingMaximum);
proto.write(
PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
- .SETTING_DEFAULT,
+ .SETTING_DEFAULT_FLOAT,
mScreenBrightnessSettingDefault);
proto.end(screenBrightnessSettingLimitsToken);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Dumpable.java b/services/core/java/com/android/server/soundtrigger_middleware/Dumpable.java
new file mode 100644
index 0000000..f9aa009
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Dumpable.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+import java.io.PrintWriter;
+
+/**
+ * An interface of an object that can generate a dump.
+ */
+interface Dumpable {
+ /**
+ * Generate a human-readable dump into the given writer.
+ * @param pw The writer.
+ */
+ void dump(@NonNull PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java b/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java
new file mode 100644
index 0000000..7f047f8
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * A collection of pretty-print utilities for data objects.
+ */
+class ObjectPrinter {
+ /** Default maximum elements to print in a collection. */
+ static public final int kDefaultMaxCollectionLength = 16;
+
+ /**
+ * Simple version of {@link #print(Object, boolean, int)} that prints an object, without
+ * recursing into sub-objects.
+ *
+ * @param obj The object to print.
+ * @return A string representing the object.
+ */
+ static String print(@Nullable Object obj) {
+ return print(obj, false, kDefaultMaxCollectionLength);
+ }
+
+ /**
+ * Pretty-prints an object.
+ *
+ * @param obj The object to print.
+ * @param deep Whether to pretty-print sub-objects (if false, just prints them
+ * with {@link Object#toString()}).
+ * @param maxCollectionLength Whenever encountering collections, maximum number of elements to
+ * print.
+ * @return A string representing the object.
+ */
+ static String print(@Nullable Object obj, boolean deep, int maxCollectionLength) {
+ StringBuilder builder = new StringBuilder();
+ print(builder, obj, deep, maxCollectionLength);
+ return builder.toString();
+ }
+
+ /**
+ * This version is suitable for use inside a toString() override of an object, e.g.:
+ * <pre><code>
+ * class MyObject {
+ * ...
+ * @Override
+ * String toString() {
+ * return ObjectPrinter.printPublicFields(this, ...);
+ * }
+ * }
+ * </code></pre>
+ *
+ * @param obj The object to print.
+ * @param deep Whether to pretty-print sub-objects (if false, just prints them
+ * with {@link Object#toString()}).
+ * @param maxCollectionLength Whenever encountering collections, maximum number of elements to
+ * print.
+ */
+ static String printPublicFields(@Nullable Object obj, boolean deep, int maxCollectionLength) {
+ StringBuilder builder = new StringBuilder();
+ printPublicFields(builder, obj, deep, maxCollectionLength);
+ return builder.toString();
+ }
+
+ /**
+ * A version of {@link #print(Object, boolean, int)} that uses a {@link StringBuilder}.
+ *
+ * @param builder StringBuilder to print into.
+ * @param obj The object to print.
+ * @param deep Whether to pretty-print sub-objects (if false, just prints them
+ * with {@link Object#toString()}).
+ * @param maxCollectionLength Whenever encountering collections, maximum number of elements to
+ * print.
+ */
+ static void print(@NonNull StringBuilder builder, @Nullable Object obj, boolean deep,
+ int maxCollectionLength) {
+ try {
+ if (obj == null) {
+ builder.append("null");
+ return;
+ }
+ if (obj instanceof Boolean) {
+ builder.append(obj.toString());
+ return;
+ }
+ if (obj instanceof Number) {
+ builder.append(obj.toString());
+ return;
+ }
+ if (obj instanceof Character) {
+ builder.append('\'');
+ builder.append(obj.toString());
+ builder.append('\'');
+ return;
+ }
+ if (obj instanceof String) {
+ builder.append('"');
+ builder.append(obj.toString());
+ builder.append('"');
+ return;
+ }
+
+ Class cls = obj.getClass();
+
+ if (Collection.class.isAssignableFrom(cls)) {
+ Collection collection = (Collection) obj;
+ builder.append("[ ");
+ int length = collection.size();
+ boolean isLong = false;
+ int i = 0;
+ for (Object child : collection) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ if (i >= maxCollectionLength) {
+ isLong = true;
+ break;
+ }
+ print(builder, child, deep, maxCollectionLength);
+ ++i;
+ }
+ if (isLong) {
+ builder.append("... (+");
+ builder.append(length - maxCollectionLength);
+ builder.append(" entries)");
+ }
+ builder.append(" ]");
+ return;
+ }
+
+ if (Map.class.isAssignableFrom(cls)) {
+ Map<?, ?> map = (Map<?, ?>) obj;
+ builder.append("< ");
+ int length = map.size();
+ boolean isLong = false;
+ int i = 0;
+ for (Map.Entry<?, ?> child : map.entrySet()) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ if (i >= maxCollectionLength) {
+ isLong = true;
+ break;
+ }
+ print(builder, child.getKey(), deep, maxCollectionLength);
+ builder.append(": ");
+ print(builder, child.getValue(), deep, maxCollectionLength);
+ ++i;
+ }
+ if (isLong) {
+ builder.append("... (+");
+ builder.append(length - maxCollectionLength);
+ builder.append(" entries)");
+ }
+ builder.append(" >");
+ return;
+ }
+
+ if (cls.isArray()) {
+ builder.append("[ ");
+ int length = Array.getLength(obj);
+ boolean isLong = false;
+ for (int i = 0; i < length; ++i) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ if (i >= maxCollectionLength) {
+ isLong = true;
+ break;
+ }
+ print(builder, Array.get(obj, i), deep, maxCollectionLength);
+ }
+ if (isLong) {
+ builder.append("... (+");
+ builder.append(length - maxCollectionLength);
+ builder.append(" entries)");
+ }
+ builder.append(" ]");
+ return;
+ }
+
+ if (!deep) {
+ builder.append(obj.toString());
+ return;
+ }
+ printPublicFields(builder, obj, deep, maxCollectionLength);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * A version of {@link #printPublicFields(Object, boolean, int)} that uses a {@link
+ * StringBuilder}.
+ *
+ * @param obj The object to print.
+ * @param deep Whether to pretty-print sub-objects (if false, just prints them
+ * with {@link Object#toString()}).
+ * @param maxCollectionLength Whenever encountering collections, maximum number of elements to
+ * print.
+ */
+ static void printPublicFields(@NonNull StringBuilder builder, @Nullable Object obj,
+ boolean deep,
+ int maxCollectionLength) {
+ try {
+ Class cls = obj.getClass();
+ builder.append("{ ");
+
+ boolean first = true;
+ for (Field fld : cls.getDeclaredFields()) {
+ int mod = fld.getModifiers();
+ if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.STATIC) == 0) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(", ");
+ }
+ builder.append(fld.getName());
+ builder.append(": ");
+ print(builder, fld.get(obj), deep, maxCollectionLength);
+ }
+ }
+ builder.append(" }");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
new file mode 100644
index 0000000..fa78cb0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedList;
+
+/**
+ * An ISoundTriggerMiddlewareService decorator, which adds logging of all API calls (and
+ * callbacks).
+ *
+ * All API methods should follow this structure:
+ * <pre><code>
+ * @Override
+ * public @NonNull ReturnType someMethod(ArgType1 arg1, ArgType2 arg2) throws ExceptionType {
+ * try {
+ * ReturnType result = mDelegate.someMethod(arg1, arg2);
+ * logReturn("someMethod", result, arg1, arg2);
+ * return result;
+ * } catch (Exception e) {
+ * logException("someMethod", e, arg1, arg2);
+ * throw e;
+ * }
+ * }
+ * </code></pre>
+ * The actual handling of these events is then done inside of {@link #logReturnWithObject(Object,
+ * String, Object, Object[])}, {@link #logVoidReturnWithObject(Object, String, Object[])} and {@link
+ * #logExceptionWithObject(Object, String, Exception, Object[])}.
+ */
+public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareService, Dumpable {
+ private static final String TAG = "SoundTriggerMiddlewareLogging";
+ private final @NonNull ISoundTriggerMiddlewareService mDelegate;
+
+ public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareService delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public @NonNull SoundTriggerModuleDescriptor[] listModules() throws RemoteException {
+ try {
+ SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
+ logReturn("listModules", result);
+ return result;
+ } catch (Exception e) {
+ logException("listModules", e);
+ throw e;
+ }
+ }
+
+ @Override
+ public @NonNull ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback)
+ throws RemoteException {
+ try {
+ ISoundTriggerModule result = mDelegate.attach(handle, new CallbackLogging(callback));
+ logReturn("attach", result, handle, callback);
+ return new ModuleLogging(result);
+ } catch (Exception e) {
+ logException("attach", e, handle, callback);
+ throw e;
+ }
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) throws RemoteException {
+ try {
+ mDelegate.setExternalCaptureState(active);
+ logVoidReturn("setExternalCaptureState", active);
+ } catch (Exception e) {
+ logException("setExternalCaptureState", e, active);
+ throw e;
+ }
+ }
+
+ @Override public IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not inteded to be used directly with Binder.");
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+
+ private void logException(String methodName, Exception ex, Object... args) {
+ logExceptionWithObject(this, methodName, ex, args);
+ }
+
+ private void logReturn(String methodName, Object retVal, Object... args) {
+ logReturnWithObject(this, methodName, retVal, args);
+ }
+
+ private void logVoidReturn(String methodName, Object... args) {
+ logVoidReturnWithObject(this, methodName, args);
+ }
+
+ private class CallbackLogging implements ISoundTriggerCallback {
+ private final ISoundTriggerCallback mDelegate;
+
+ private CallbackLogging(ISoundTriggerCallback delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public void onRecognition(int modelHandle, RecognitionEvent event) throws RemoteException {
+ try {
+ mDelegate.onRecognition(modelHandle, event);
+ logVoidReturn("onRecognition", modelHandle, event);
+ } catch (Exception e) {
+ logException("onRecognition", e, modelHandle, event);
+ throw e;
+ }
+ }
+
+ @Override
+ public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event)
+ throws RemoteException {
+ try {
+ mDelegate.onPhraseRecognition(modelHandle, event);
+ logVoidReturn("onPhraseRecognition", modelHandle, event);
+ } catch (Exception e) {
+ logException("onPhraseRecognition", e, modelHandle, event);
+ throw e;
+ }
+ }
+
+ @Override
+ public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ try {
+ mDelegate.onRecognitionAvailabilityChange(available);
+ logVoidReturn("onRecognitionAvailabilityChange", available);
+ } catch (Exception e) {
+ logException("onRecognitionAvailabilityChange", e, available);
+ throw e;
+ }
+ }
+
+ @Override
+ public void onModuleDied() throws RemoteException {
+ try {
+ mDelegate.onModuleDied();
+ logVoidReturn("onModuleDied");
+ } catch (Exception e) {
+ logException("onModuleDied", e);
+ throw e;
+ }
+ }
+
+ private void logException(String methodName, Exception ex, Object... args) {
+ logExceptionWithObject(this, methodName, ex, args);
+ }
+
+ private void logVoidReturn(String methodName, Object... args) {
+ logVoidReturnWithObject(this, methodName, args);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return mDelegate.asBinder();
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+ }
+
+ private class ModuleLogging implements ISoundTriggerModule {
+ private final ISoundTriggerModule mDelegate;
+
+ private ModuleLogging(ISoundTriggerModule delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public int loadModel(SoundModel model) throws RemoteException {
+ try {
+ int result = mDelegate.loadModel(model);
+ logReturn("loadModel", result, model);
+ return result;
+ } catch (Exception e) {
+ logException("loadModel", e, model);
+ throw e;
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(PhraseSoundModel model) throws RemoteException {
+ try {
+ int result = mDelegate.loadPhraseModel(model);
+ logReturn("loadPhraseModel", result, model);
+ return result;
+ } catch (Exception e) {
+ logException("loadPhraseModel", e, model);
+ throw e;
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) throws RemoteException {
+ try {
+ mDelegate.unloadModel(modelHandle);
+ logVoidReturn("unloadModel", modelHandle);
+ } catch (Exception e) {
+ logException("unloadModel", e, modelHandle);
+ throw e;
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, RecognitionConfig config)
+ throws RemoteException {
+ try {
+ mDelegate.startRecognition(modelHandle, config);
+ logVoidReturn("startRecognition", modelHandle, config);
+ } catch (Exception e) {
+ logException("startRecognition", e, modelHandle, config);
+ throw e;
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) throws RemoteException {
+ try {
+ mDelegate.stopRecognition(modelHandle);
+ logVoidReturn("stopRecognition", modelHandle);
+ } catch (Exception e) {
+ logException("stopRecognition", e, modelHandle);
+ throw e;
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) throws RemoteException {
+ try {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ logVoidReturn("forceRecognitionEvent", modelHandle);
+ } catch (Exception e) {
+ logException("forceRecognitionEvent", e, modelHandle);
+ throw e;
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value)
+ throws RemoteException {
+ try {
+ mDelegate.setModelParameter(modelHandle, modelParam, value);
+ logVoidReturn("setModelParameter", modelHandle, modelParam, value);
+ } catch (Exception e) {
+ logException("setModelParameter", e, modelHandle, modelParam, value);
+ throw e;
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ try {
+ int result = mDelegate.getModelParameter(modelHandle, modelParam);
+ logReturn("getModelParameter", result, modelHandle, modelParam);
+ return result;
+ } catch (Exception e) {
+ logException("getModelParameter", e, modelHandle, modelParam);
+ throw e;
+ }
+ }
+
+ @Override
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam)
+ throws RemoteException {
+ try {
+ ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
+ modelParam);
+ logReturn("queryModelParameterSupport", result, modelHandle, modelParam);
+ return result;
+ } catch (Exception e) {
+ logException("queryModelParameterSupport", e, modelHandle, modelParam);
+ throw e;
+ }
+ }
+
+ @Override
+ public void detach() throws RemoteException {
+ try {
+ mDelegate.detach();
+ logVoidReturn("detach");
+ } catch (Exception e) {
+ logException("detach", e);
+ throw e;
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return mDelegate.asBinder();
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+
+ private void logException(String methodName, Exception ex, Object... args) {
+ logExceptionWithObject(this, methodName, ex, args);
+ }
+
+ private void logReturn(String methodName, Object retVal, Object... args) {
+ logReturnWithObject(this, methodName, retVal, args);
+ }
+
+ private void logVoidReturn(String methodName, Object... args) {
+ logVoidReturnWithObject(this, methodName, args);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // Actual logging logic below.
+ private static final int NUM_EVENTS_TO_DUMP = 64;
+ private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
+ private final @NonNull LinkedList<Event> mLastEvents = new LinkedList<>();
+
+ static private class Event {
+ public final long timestamp = System.currentTimeMillis();
+ public final String message;
+
+ private Event(String message) {
+ this.message = message;
+ }
+ }
+
+ private static String printArgs(@NonNull Object[] args) {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < args.length; ++i) {
+ if (i > 0) {
+ result.append(", ");
+ }
+ printObject(result, args[i]);
+ }
+ return result.toString();
+ }
+
+ private static void printObject(@NonNull StringBuilder builder, @Nullable Object obj) {
+ if (obj instanceof Parcelable) {
+ ObjectPrinter.print(builder, obj, true, 16);
+ } else {
+ builder.append(obj.toString());
+ }
+ }
+
+ private static String printObject(@Nullable Object obj) {
+ StringBuilder builder = new StringBuilder();
+ printObject(builder, obj);
+ return builder.toString();
+ }
+
+ private void logReturnWithObject(@NonNull Object object, String methodName,
+ @Nullable Object retVal,
+ @NonNull Object[] args) {
+ final String message = String.format("%s[this=%s, caller=%d/%d](%s) -> %s", methodName,
+ object,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ printArgs(args),
+ printObject(retVal));
+ Log.i(TAG, message);
+ appendMessage(message);
+ }
+
+ private void logVoidReturnWithObject(@NonNull Object object, @NonNull String methodName,
+ @NonNull Object[] args) {
+ final String message = String.format("%s[this=%s, caller=%d/%d](%s)", methodName,
+ object,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ printArgs(args));
+ Log.i(TAG, message);
+ appendMessage(message);
+ }
+
+ private void logExceptionWithObject(@NonNull Object object, @NonNull String methodName,
+ @NonNull Exception ex,
+ Object[] args) {
+ final String message = String.format("%s[this=%s, caller=%d/%d](%s) threw", methodName,
+ object,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ printArgs(args));
+ Log.e(TAG, message, ex);
+ appendMessage(message + " " + ex.toString());
+ }
+
+ private void appendMessage(@NonNull String message) {
+ Event event = new Event(message);
+ synchronized (mLastEvents) {
+ if (mLastEvents.size() > NUM_EVENTS_TO_DUMP) {
+ mLastEvents.remove();
+ }
+ mLastEvents.add(event);
+ }
+ }
+
+ @Override public void dump(PrintWriter pw) {
+ pw.println();
+ pw.println("=========================================");
+ pw.println("Last events");
+ pw.println("=========================================");
+ synchronized (mLastEvents) {
+ for (Event event : mLastEvents) {
+ pw.print(DATE_FORMAT.format(new Date(event.timestamp)));
+ pw.print('\t');
+ pw.println(event.message);
+ }
+ }
+ pw.println();
+
+ if (mDelegate instanceof Dumpable) {
+ ((Dumpable) mDelegate).dump(pw);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 1ed97be..0d8fc76 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -16,91 +16,40 @@
package com.android.server.soundtrigger_middleware;
-import android.Manifest;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
-import android.content.PermissionChecker;
import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
import android.media.soundtrigger_middleware.ModelParameterRange;
-import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
import android.media.soundtrigger_middleware.PhraseSoundModel;
import android.media.soundtrigger_middleware.RecognitionConfig;
-import android.media.soundtrigger_middleware.RecognitionEvent;
-import android.media.soundtrigger_middleware.RecognitionStatus;
import android.media.soundtrigger_middleware.SoundModel;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.media.soundtrigger_middleware.Status;
import android.os.RemoteException;
-import android.os.ServiceSpecificException;
import android.util.Log;
-import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Objects;
-import java.util.Set;
/**
* This is a wrapper around an {@link ISoundTriggerMiddlewareService} implementation, which exposes
- * it as a Binder service and enforces permissions and correct usage by the client, as well as makes
- * sure that exceptions representing a server malfunction do not get sent to the client.
+ * it as a Binder service.
* <p>
- * This is intended to extract the non-business logic out of the underlying implementation and thus
- * make it easier to maintain each one of those separate aspects. A design trade-off is being made
- * here, in that this class would need to essentially eavesdrop on all the client-server
- * communication and retain all state known to the client, while the client doesn't necessarily care
- * about all of it, and while the server has its own representation of this information. However,
- * in this case, this is a small amount of data, and the benefits in code elegance seem worth it.
- * There is also some additional cost in employing a simplistic locking mechanism here, but
- * following the same line of reasoning, the benefits in code simplicity outweigh it.
+ * This is intended to facilitate a pattern of decorating the core implementation (business logic)
+ * of the interface with every decorator implementing a different aspect of the service, such as
+ * validation and logging. This class acts as the top-level decorator, which also adds the binder-
+ * related functionality (hence, it extends ISoundTriggerMiddlewareService.Stub as rather than
+ * implements ISoundTriggerMiddlewareService), and does the same thing for child interfaces
+ * returned.
* <p>
- * Every public method in this class, overriding an interface method, must follow the following
- * pattern:
- * <code><pre>
- * @Override public T method(S arg) {
- * // Permission check.
- * checkPermissions();
- * // Input validation.
- * ValidationUtil.validateS(arg);
- * synchronized (this) {
- * // State validation.
- * if (...state is not valid for this call...) {
- * throw new IllegalStateException("State is invalid because...");
- * }
- * // From here on, every exception isn't client's fault.
- * try {
- * T result = mDelegate.method(arg);
- * // Update state.;
- * ...
- * return result;
- * } catch (Exception e) {
- * throw handleException(e);
- * }
- * }
- * }
- * </pre></code>
- * Following this patterns ensures a consistent and rigorous handling of all aspects associated
- * with client-server separation.
- * <p>
- * <b>Exception handling approach:</b><br>
- * We make sure all client faults (permissions, argument and state validation) happen first, and
- * would throw {@link SecurityException}, {@link IllegalArgumentException}/
- * {@link NullPointerException} or {@link
- * IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
- * will get sent back to the client.<br>
- * Once this is done, any subsequent fault is considered a server fault. Only {@link
- * RecoverableException}s thrown by the implementation are special-cased: they would get sent back
- * to the caller as a {@link ServiceSpecificException}, which is the behavior of Binder. Any other
- * exception gets wrapped with a {@link InternalServerError}, which is specifically chosen as a type
- * that <b>does NOT</b> get forwarded by binder. Those exceptions would be handled by a high-level
- * exception handler on the server side, typically resulting in rebooting the server.
+ * The inner class {@link Lifecycle} acts as both a factory, composing the various aspect-decorators
+ * to create a full-featured implementation, as well as as an entry-point for presenting this
+ * implementation as a system service.
* <p>
* <b>Exposing this service as a System Service:</b><br>
* Insert this line into {@link com.android.server.SystemServer}:
@@ -113,224 +62,100 @@
public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
static private final String TAG = "SoundTriggerMiddlewareService";
+ @NonNull
private final ISoundTriggerMiddlewareService mDelegate;
- private final Context mContext;
- private Set<Integer> mModuleHandles;
/**
* Constructor for internal use only. Could be exposed for testing purposes in the future.
* Users should access this class via {@link Lifecycle}.
*/
- private SoundTriggerMiddlewareService(
- @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
- mDelegate = delegate;
- mContext = context;
- }
-
- /**
- * Generic exception handling for exceptions thrown by the underlying implementation.
- *
- * Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
- * by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
- * (<b>not</b> passed by Binder to the caller).
- * <p>
- * Typical usage:
- * <code><pre>
- * try {
- * ... Do server operations ...
- * } catch (Exception e) {
- * throw handleException(e);
- * }
- * </pre></code>
- */
- private static @NonNull
- RuntimeException handleException(@NonNull Exception e) {
- if (e instanceof RecoverableException) {
- throw new ServiceSpecificException(((RecoverableException) e).errorCode,
- e.getMessage());
- }
- throw new InternalServerError(e);
+ private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareService delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
}
@Override
public @NonNull
- SoundTriggerModuleDescriptor[] listModules() {
- // Permission check.
- checkPermissions();
- // Input validation (always valid).
-
- synchronized (this) {
- // State validation (always valid).
-
- // From here on, every exception isn't client's fault.
- try {
- SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
- mModuleHandles = new HashSet<>(result.length);
- for (SoundTriggerModuleDescriptor desc : result) {
- mModuleHandles.add(desc.handle);
- }
- return result;
- } catch (Exception e) {
- throw handleException(e);
- }
- }
+ SoundTriggerModuleDescriptor[] listModules() throws RemoteException {
+ return mDelegate.listModules();
}
@Override
public @NonNull
- ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
- // Permission check.
- checkPermissions();
- // Input validation.
- Objects.requireNonNull(callback);
- Objects.requireNonNull(callback.asBinder());
-
- synchronized (this) {
- // State validation.
- if (mModuleHandles == null) {
- throw new IllegalStateException(
- "Client must call listModules() prior to attaching.");
- }
- if (!mModuleHandles.contains(handle)) {
- throw new IllegalArgumentException("Invalid handle: " + handle);
- }
-
- // From here on, every exception isn't client's fault.
- try {
- ModuleService moduleService = new ModuleService(callback);
- moduleService.attach(mDelegate.attach(handle, moduleService));
- return moduleService;
- } catch (Exception e) {
- throw handleException(e);
- }
- }
+ ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback)
+ throws RemoteException {
+ return new ModuleService(mDelegate.attach(handle, callback));
}
@Override
- public void setExternalCaptureState(boolean active) {
- // Permission check.
- checkPreemptPermissions();
- // Input validation (always valid).
+ public void setExternalCaptureState(boolean active) throws RemoteException {
+ mDelegate.setExternalCaptureState(active);
+ }
- synchronized (this) {
- // State validation (always valid).
-
- // From here on, every exception isn't client's fault.
- try {
- mDelegate.setExternalCaptureState(active);
- } catch (Exception e) {
- throw handleException(e);
- }
+ @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ if (mDelegate instanceof Dumpable) {
+ ((Dumpable) mDelegate).dump(fout);
}
}
- /**
- * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
- * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
- * caller temporarily doesn't have the right permissions to use this service.
- */
- private void checkPermissions() {
- enforcePermission(Manifest.permission.RECORD_AUDIO);
- enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
- }
+ private final static class ModuleService extends ISoundTriggerModule.Stub {
+ private final ISoundTriggerModule mDelegate;
- /**
- * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
- * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
- * caller temporarily doesn't have the right permissions to preempt active sound trigger
- * sessions.
- */
- private void checkPreemptPermissions() {
- enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
- }
-
- /**
- * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
- * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
- * caller temporarily doesn't have the given permission.
- *
- * @param permission The permission to check.
- */
- private void enforcePermission(String permission) {
- final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
- permission);
- switch (status) {
- case PermissionChecker.PERMISSION_GRANTED:
- return;
- case PermissionChecker.PERMISSION_HARD_DENIED:
- throw new SecurityException(
- String.format("Caller must have the %s permission.", permission));
- case PermissionChecker.PERMISSION_SOFT_DENIED:
- throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
- String.format("Caller must have the %s permission.", permission));
- default:
- throw new InternalServerError(
- new RuntimeException("Unexpected perimission check result."));
- }
- }
-
- /** State of a sound model. */
- static class ModelState {
- /** Activity state of a sound model. */
- enum Activity {
- /** Model is loaded, recognition is inactive. */
- LOADED,
- /** Model is loaded, recognition is active. */
- ACTIVE
+ private ModuleService(ISoundTriggerModule delegate) {
+ mDelegate = delegate;
}
- /** Activity state. */
- Activity activityState = Activity.LOADED;
-
- /**
- * A map of known parameter support. A missing key means we don't know yet whether the
- * parameter is supported. A null value means it is known to not be supported. A non-null
- * value indicates the valid value range.
- */
- private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
-
- /**
- * Check that the given parameter is known to be supported for this model.
- *
- * @param modelParam The parameter key.
- */
- void checkSupported(int modelParam) {
- if (!parameterSupport.containsKey(modelParam)) {
- throw new IllegalStateException("Parameter has not been checked for support.");
- }
- ModelParameterRange range = parameterSupport.get(modelParam);
- if (range == null) {
- throw new IllegalArgumentException("Paramater is not supported.");
- }
+ @Override
+ public int loadModel(SoundModel model) throws RemoteException {
+ return mDelegate.loadModel(model);
}
- /**
- * Check that the given parameter is known to be supported for this model and that the given
- * value is a valid value for it.
- *
- * @param modelParam The parameter key.
- * @param value The value.
- */
- void checkSupported(int modelParam, int value) {
- if (!parameterSupport.containsKey(modelParam)) {
- throw new IllegalStateException("Parameter has not been checked for support.");
- }
- ModelParameterRange range = parameterSupport.get(modelParam);
- if (range == null) {
- throw new IllegalArgumentException("Paramater is not supported.");
- }
- Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
- "value");
+ @Override
+ public int loadPhraseModel(PhraseSoundModel model) throws RemoteException {
+ return mDelegate.loadPhraseModel(model);
}
- /**
- * Update support state for the given parameter for this model.
- *
- * @param modelParam The parameter key.
- * @param range The parameter value range, or null if not supported.
- */
- void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
- parameterSupport.put(modelParam, range);
+ @Override
+ public void unloadModel(int modelHandle) throws RemoteException {
+ mDelegate.unloadModel(modelHandle);
+ ;
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, RecognitionConfig config)
+ throws RemoteException {
+ mDelegate.startRecognition(modelHandle, config);
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) throws RemoteException {
+ mDelegate.stopRecognition(modelHandle);
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) throws RemoteException {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value)
+ throws RemoteException {
+ mDelegate.setModelParameter(modelHandle, modelParam, value);
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ return mDelegate.getModelParameter(modelHandle, modelParam);
+ }
+
+ @Override
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam)
+ throws RemoteException {
+ return mDelegate.queryModelParameterSupport(modelHandle, modelParam);
+ }
+
+ @Override
+ public void detach() throws RemoteException {
+ mDelegate.detach();
}
}
@@ -355,395 +180,11 @@
publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
new SoundTriggerMiddlewareService(
- new SoundTriggerMiddlewareImpl(factories,
- new AudioSessionProviderImpl()),
- getContext()));
- }
- }
-
- /**
- * A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
- * mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
- */
- private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
- DeathRecipient {
- private final ISoundTriggerCallback mCallback;
- private ISoundTriggerModule mDelegate;
- private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
-
- ModuleService(@NonNull ISoundTriggerCallback callback) {
- mCallback = callback;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- void attach(@NonNull ISoundTriggerModule delegate) {
- mDelegate = delegate;
- }
-
- @Override
- public int loadModel(@NonNull SoundModel model) {
- // Permission check.
- checkPermissions();
- // Input validation.
- ValidationUtil.validateGenericModel(model);
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has been detached.");
- }
-
- // From here on, every exception isn't client's fault.
- try {
- int handle = mDelegate.loadModel(model);
- mLoadedModels.put(handle, new ModelState());
- return handle;
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- @Override
- public int loadPhraseModel(@NonNull PhraseSoundModel model) {
- // Permission check.
- checkPermissions();
- // Input validation.
- ValidationUtil.validatePhraseModel(model);
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has been detached.");
- }
-
- // From here on, every exception isn't client's fault.
- try {
- int handle = mDelegate.loadPhraseModel(model);
- mLoadedModels.put(handle, new ModelState());
- return handle;
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- @Override
- public void unloadModel(int modelHandle) {
- // Permission check.
- checkPermissions();
- // Input validation (always valid).
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has been detached.");
- }
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState == null) {
- throw new IllegalStateException("Invalid handle: " + modelHandle);
- }
- if (modelState.activityState != ModelState.Activity.LOADED) {
- throw new IllegalStateException("Model with handle: " + modelHandle
- + " has invalid state for unloading: " + modelState.activityState);
- }
-
- // From here on, every exception isn't client's fault.
- try {
- mDelegate.unloadModel(modelHandle);
- mLoadedModels.remove(modelHandle);
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- @Override
- public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
- // Permission check.
- checkPermissions();
- // Input validation.
- ValidationUtil.validateRecognitionConfig(config);
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has been detached.");
- }
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState == null) {
- throw new IllegalStateException("Invalid handle: " + modelHandle);
- }
- if (modelState.activityState != ModelState.Activity.LOADED) {
- throw new IllegalStateException("Model with handle: " + modelHandle
- + " has invalid state for starting recognition: "
- + modelState.activityState);
- }
-
- // From here on, every exception isn't client's fault.
- try {
- mDelegate.startRecognition(modelHandle, config);
- modelState.activityState = ModelState.Activity.ACTIVE;
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- @Override
- public void stopRecognition(int modelHandle) {
- // Permission check.
- checkPermissions();
- // Input validation (always valid).
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has been detached.");
- }
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState == null) {
- throw new IllegalStateException("Invalid handle: " + modelHandle);
- }
- // stopRecognition is idempotent - no need to check model state.
-
- // From here on, every exception isn't client's fault.
- try {
- mDelegate.stopRecognition(modelHandle);
- modelState.activityState = ModelState.Activity.LOADED;
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- @Override
- public void forceRecognitionEvent(int modelHandle) {
- // Permission check.
- checkPermissions();
- // Input validation (always valid).
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has been detached.");
- }
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState == null) {
- throw new IllegalStateException("Invalid handle: " + modelHandle);
- }
- // forceRecognitionEvent is idempotent - no need to check model state.
-
- // From here on, every exception isn't client's fault.
- try {
- mDelegate.forceRecognitionEvent(modelHandle);
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- @Override
- public void setModelParameter(int modelHandle, int modelParam, int value) {
- // Permission check.
- checkPermissions();
- // Input validation.
- ValidationUtil.validateModelParameter(modelParam);
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has been detached.");
- }
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState == null) {
- throw new IllegalStateException("Invalid handle: " + modelHandle);
- }
- modelState.checkSupported(modelParam, value);
-
- // From here on, every exception isn't client's fault.
- try {
- mDelegate.setModelParameter(modelHandle, modelParam, value);
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- @Override
- public int getModelParameter(int modelHandle, int modelParam) {
- // Permission check.
- checkPermissions();
- // Input validation.
- ValidationUtil.validateModelParameter(modelParam);
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has been detached.");
- }
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState == null) {
- throw new IllegalStateException("Invalid handle: " + modelHandle);
- }
- modelState.checkSupported(modelParam);
-
- // From here on, every exception isn't client's fault.
- try {
- return mDelegate.getModelParameter(modelHandle, modelParam);
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- @Override
- @Nullable
- public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
- // Permission check.
- checkPermissions();
- // Input validation.
- ValidationUtil.validateModelParameter(modelParam);
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has been detached.");
- }
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState == null) {
- throw new IllegalStateException("Invalid handle: " + modelHandle);
- }
-
- // From here on, every exception isn't client's fault.
- try {
- ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
- modelParam);
- modelState.updateParameterSupport(modelParam, result);
- return result;
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- @Override
- public void detach() {
- // Permission check.
- checkPermissions();
- // Input validation (always valid).
-
- synchronized (this) {
- // State validation.
- if (mDelegate == null) {
- throw new IllegalStateException("Module has already been detached.");
- }
- if (!mLoadedModels.isEmpty()) {
- throw new IllegalStateException("Cannot detach while models are loaded.");
- }
-
- // From here on, every exception isn't client's fault.
- try {
- detachInternal();
- } catch (Exception e) {
- throw handleException(e);
- }
- }
- }
-
- private void detachInternal() {
- try {
- mDelegate.detach();
- mDelegate = null;
- mCallback.asBinder().unlinkToDeath(this, 0);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////
- // Callbacks
-
- @Override
- public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
- synchronized (this) {
- if (event.status != RecognitionStatus.FORCED) {
- mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
- }
- try {
- mCallback.onRecognition(modelHandle, event);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
- }
- }
- }
-
- @Override
- public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
- synchronized (this) {
- if (event.common.status != RecognitionStatus.FORCED) {
- mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
- }
- try {
- mCallback.onPhraseRecognition(modelHandle, event);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
- }
- }
- }
-
- @Override
- public void onRecognitionAvailabilityChange(boolean available) {
- synchronized (this) {
- try {
- mCallback.onRecognitionAvailabilityChange(available);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
- }
- }
- }
-
- @Override
- public void onModuleDied() {
- synchronized (this) {
- try {
- mCallback.onModuleDied();
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
- }
- }
- }
-
- @Override
- public void binderDied() {
- // This is called whenever our client process dies.
- synchronized (this) {
- try {
- // Gracefully stop all active recognitions and unload the models.
- for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
- if (entry.getValue().activityState == ModelState.Activity.ACTIVE) {
- mDelegate.stopRecognition(entry.getKey());
- }
- mDelegate.unloadModel(entry.getKey());
- }
- // Detach.
- detachInternal();
- } catch (Exception e) {
- throw handleException(e);
- }
- }
+ new SoundTriggerMiddlewareLogging(
+ new SoundTriggerMiddlewareValidation(
+ new SoundTriggerMiddlewareImpl(factories,
+ new AudioSessionProviderImpl()),
+ getContext()))));
}
}
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
new file mode 100644
index 0000000..c45f37d
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -0,0 +1,784 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.PermissionChecker;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This is a decorator of an {@link ISoundTriggerMiddlewareService}, which enforces permissions and
+ * correct usage by the client, as well as makes sure that exceptions representing a server
+ * malfunction do not get sent to the client.
+ * <p>
+ * This is intended to extract the non-business logic out of the underlying implementation and thus
+ * make it easier to maintain each one of those separate aspects. A design trade-off is being made
+ * here, in that this class would need to essentially eavesdrop on all the client-server
+ * communication and retain all state known to the client, while the client doesn't necessarily care
+ * about all of it, and while the server has its own representation of this information. However,
+ * in this case, this is a small amount of data, and the benefits in code elegance seem worth it.
+ * There is also some additional cost in employing a simplistic locking mechanism here, but
+ * following the same line of reasoning, the benefits in code simplicity outweigh it.
+ * <p>
+ * Every public method in this class, overriding an interface method, must follow the following
+ * pattern:
+ * <code><pre>
+ * @Override public T method(S arg) {
+ * // Permission check.
+ * checkPermissions();
+ * // Input validation.
+ * ValidationUtil.validateS(arg);
+ * synchronized (this) {
+ * // State validation.
+ * if (...state is not valid for this call...) {
+ * throw new IllegalStateException("State is invalid because...");
+ * }
+ * // From here on, every exception isn't client's fault.
+ * try {
+ * T result = mDelegate.method(arg);
+ * // Update state.;
+ * ...
+ * return result;
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * }
+ * }
+ * </pre></code>
+ * Following this patterns ensures a consistent and rigorous handling of all aspects associated
+ * with client-server separation.
+ * <p>
+ * <b>Exception handling approach:</b><br>
+ * We make sure all client faults (permissions, argument and state validation) happen first, and
+ * would throw {@link SecurityException}, {@link IllegalArgumentException}/
+ * {@link NullPointerException} or {@link
+ * IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
+ * will get sent back to the client.<br>
+ * Once this is done, any subsequent fault is considered a server fault. Only {@link
+ * RecoverableException}s thrown by the implementation are special-cased: they would get sent back
+ * to the caller as a {@link ServiceSpecificException}, which is the behavior of Binder. Any other
+ * exception gets wrapped with a {@link InternalServerError}, which is specifically chosen as a type
+ * that <b>does NOT</b> get forwarded by binder. Those exceptions would be handled by a high-level
+ * exception handler on the server side, typically resulting in rebooting the server.
+ *
+ * {@hide}
+ */
+public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddlewareService, Dumpable {
+ private static final String TAG = "SoundTriggerMiddlewareValidation";
+
+ private final @NonNull ISoundTriggerMiddlewareService mDelegate;
+ private final @NonNull Context mContext;
+ private Map<Integer, Set<ModuleService>> mModules;
+
+ public SoundTriggerMiddlewareValidation(
+ @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
+ mDelegate = delegate;
+ mContext = context;
+ }
+
+ /**
+ * Generic exception handling for exceptions thrown by the underlying implementation.
+ *
+ * Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
+ * by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
+ * (<b>not</b> passed by Binder to the caller).
+ * <p>
+ * Typical usage:
+ * <code><pre>
+ * try {
+ * ... Do server operations ...
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * </pre></code>
+ */
+ static @NonNull
+ RuntimeException handleException(@NonNull Exception e) {
+ if (e instanceof RecoverableException) {
+ throw new ServiceSpecificException(((RecoverableException) e).errorCode,
+ e.getMessage());
+ }
+ throw new InternalServerError(e);
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
+ mModules = new HashMap<>(result.length);
+ for (SoundTriggerModuleDescriptor desc : result) {
+ mModules.put(desc.handle, new HashSet<>());
+ }
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public @NonNull ISoundTriggerModule attach(int handle,
+ @NonNull ISoundTriggerCallback callback) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ Objects.requireNonNull(callback);
+ Objects.requireNonNull(callback.asBinder());
+
+ synchronized (this) {
+ // State validation.
+ if (mModules == null) {
+ throw new IllegalStateException(
+ "Client must call listModules() prior to attaching.");
+ }
+ if (!mModules.containsKey(handle)) {
+ throw new IllegalArgumentException("Invalid handle: " + handle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModuleService moduleService =
+ new ModuleService(handle, callback);
+ moduleService.attach(mDelegate.attach(handle, moduleService));
+ return moduleService;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) {
+ // Permission check.
+ checkPreemptPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setExternalCaptureState(active);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * caller temporarily doesn't have the right permissions to use this service.
+ */
+ void checkPermissions() {
+ enforcePermission(Manifest.permission.RECORD_AUDIO);
+ enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * caller temporarily doesn't have the right permissions to preempt active sound trigger
+ * sessions.
+ */
+ void checkPreemptPermissions() {
+ enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * caller temporarily doesn't have the given permission.
+ *
+ * @param permission The permission to check.
+ */
+ void enforcePermission(String permission) {
+ final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
+ permission);
+ switch (status) {
+ case PermissionChecker.PERMISSION_GRANTED:
+ return;
+ case PermissionChecker.PERMISSION_HARD_DENIED:
+ throw new SecurityException(
+ String.format("Caller must have the %s permission.", permission));
+ case PermissionChecker.PERMISSION_SOFT_DENIED:
+ throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
+ String.format("Caller must have the %s permission.", permission));
+ default:
+ throw new InternalServerError(
+ new RuntimeException("Unexpected perimission check result."));
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not inteded to be used directly with Binder.");
+ }
+
+ @Override public void dump(PrintWriter pw) {
+ synchronized (this) {
+ if (mModules != null) {
+ for (int handle : mModules.keySet()) {
+ pw.println("=========================================");
+ pw.printf("Active sessions for module %d", handle);
+ pw.println();
+ pw.println("=========================================");
+ for (ModuleService session : mModules.get(handle)) {
+ session.dump(pw);
+ }
+ }
+ }
+ }
+ pw.println();
+
+ if (mDelegate instanceof Dumpable) {
+ ((Dumpable) mDelegate).dump(pw);
+ }
+
+ }
+
+ /** State of a sound model. */
+ static class ModelState {
+ /** Activity state of a sound model. */
+ enum Activity {
+ /** Model is loaded, recognition is inactive. */
+ LOADED,
+ /** Model is loaded, recognition is active. */
+ ACTIVE
+ }
+
+ /** Activity state. */
+ Activity activityState = Activity.LOADED;
+
+ /**
+ * A map of known parameter support. A missing key means we don't know yet whether the
+ * parameter is supported. A null value means it is known to not be supported. A non-null
+ * value indicates the valid value range.
+ */
+ private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
+
+ /**
+ * Check that the given parameter is known to be supported for this model.
+ *
+ * @param modelParam The parameter key.
+ */
+ void checkSupported(int modelParam) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ }
+
+ /**
+ * Check that the given parameter is known to be supported for this model and that the given
+ * value is a valid value for it.
+ *
+ * @param modelParam The parameter key.
+ * @param value The value.
+ */
+ void checkSupported(int modelParam, int value) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
+ "value");
+ }
+
+ /**
+ * Update support state for the given parameter for this model.
+ *
+ * @param modelParam The parameter key.
+ * @param range The parameter value range, or null if not supported.
+ */
+ void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+ parameterSupport.put(modelParam, range);
+ }
+ }
+
+ /**
+ * A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
+ * mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
+ */
+ private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
+ IBinder.DeathRecipient {
+ private final ISoundTriggerCallback mCallback;
+ private ISoundTriggerModule mDelegate;
+ private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+ private final int mHandle;
+
+ ModuleService(int handle, @NonNull ISoundTriggerCallback callback) {
+ mCallback = callback;
+ mHandle = handle;
+ try {
+ mCallback.asBinder().linkToDeath(null, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ void attach(@NonNull ISoundTriggerModule delegate) {
+ mDelegate = delegate;
+ mModules.get(mHandle).add(this);
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateGenericModel(model);
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validatePhraseModel(model);
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadPhraseModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(
+ modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState
+ != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for unloading: " + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.unloadModel(modelHandle);
+ mLoadedModels.remove(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateRecognitionConfig(config);
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(
+ modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState
+ != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for starting recognition: "
+ + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.startRecognition(modelHandle, config);
+ modelState.activityState =
+ ModelState.Activity.ACTIVE;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(
+ modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // stopRecognition is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.stopRecognition(modelHandle);
+ modelState.activityState =
+ ModelState.Activity.LOADED;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(
+ modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // forceRecognitionEvent is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(
+ modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam, value);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setModelParameter(modelHandle, modelParam, value);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(
+ modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ return mDelegate.getModelParameter(modelHandle, modelParam);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(
+ modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
+ modelParam);
+ modelState.updateParameterSupport(modelParam, result);
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void detach() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has already been detached.");
+ }
+ if (!mLoadedModels.isEmpty()) {
+ throw new IllegalStateException("Cannot detach while models are loaded.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+
+ private void detachInternal() {
+ try {
+ mDelegate.detach();
+ mDelegate = null;
+ mCallback.asBinder().unlinkToDeath(null, 0);
+ mModules.get(mHandle).remove(this);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.printf("Loaded models for session %s (handle, active)", toString());
+ pw.println();
+ pw.println("-------------------------------");
+ for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
+ pw.print(entry.getKey());
+ pw.print('\t');
+ pw.print(entry.getValue().activityState.name());
+ pw.println();
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Callbacks
+
+ @Override
+ public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ if (event.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState =
+ ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ if (event.common.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState =
+ ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onPhraseRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRecognitionAvailabilityChange(boolean available) {
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ try {
+ mCallback.onRecognitionAvailabilityChange(available);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onModuleDied() {
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ try {
+ mCallback.onModuleDied();
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
+ }
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ // This is called whenever our client process dies.
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ try {
+ // Gracefully stop all active recognitions and unload the models.
+ for (Map.Entry<Integer, ModelState> entry :
+ mLoadedModels.entrySet()) {
+ if (entry.getValue().activityState
+ == ModelState.Activity.ACTIVE) {
+ mDelegate.stopRecognition(entry.getKey());
+ }
+ mDelegate.unloadModel(entry.getKey());
+ }
+ // Detach.
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index aa1558e..d6390184 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -123,7 +123,6 @@
*/
synchronized @NonNull
ISoundTriggerModule attach(@NonNull ISoundTriggerCallback callback) {
- Log.d(TAG, "attach()");
Session session = new Session(callback);
mActiveSessions.add(session);
return session;
@@ -149,8 +148,6 @@
* @param active true iff external capture is active.
*/
synchronized void setExternalCaptureState(boolean active) {
- Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
-
if (mProperties.concurrentCapture) {
// If we support concurrent capture, we don't care about any of this.
return;
@@ -235,7 +232,6 @@
@Override
public void detach() {
- Log.d(TAG, "detach()");
synchronized (SoundTriggerModule.this) {
if (mCallback == null) {
return;
@@ -247,8 +243,6 @@
@Override
public int loadModel(@NonNull SoundModel model) {
- Log.d(TAG, String.format("loadModel(model=%s)", model));
-
// We must do this outside the lock, to avoid possible deadlocks with the remote process
// that provides the audio sessions, which may also be calling into us.
SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
@@ -276,8 +270,6 @@
@Override
public int loadPhraseModel(@NonNull PhraseSoundModel model) {
- Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
-
// We must do this outside the lock, to avoid possible deadlocks with the remote process
// that provides the audio sessions, which may also be calling into us.
SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
@@ -306,10 +298,7 @@
@Override
public void unloadModel(int modelHandle) {
- Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
-
int sessionId;
-
synchronized (SoundTriggerModule.this) {
checkValid();
sessionId = mLoadedModels.get(modelHandle).unload();
@@ -323,8 +312,6 @@
@Override
public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
- Log.d(TAG,
- String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
synchronized (SoundTriggerModule.this) {
checkValid();
mLoadedModels.get(modelHandle).startRecognition(config);
@@ -333,7 +320,6 @@
@Override
public void stopRecognition(int modelHandle) {
- Log.d(TAG, String.format("stopRecognition(handle=%d)", modelHandle));
synchronized (SoundTriggerModule.this) {
mLoadedModels.get(modelHandle).stopRecognition();
}
@@ -341,7 +327,6 @@
@Override
public void forceRecognitionEvent(int modelHandle) {
- Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
synchronized (SoundTriggerModule.this) {
checkValid();
mLoadedModels.get(modelHandle).forceRecognitionEvent();
@@ -350,9 +335,6 @@
@Override
public void setModelParameter(int modelHandle, int modelParam, int value) {
- Log.d(TAG,
- String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
- modelParam, value));
synchronized (SoundTriggerModule.this) {
checkValid();
mLoadedModels.get(modelHandle).setParameter(modelParam, value);
@@ -361,8 +343,6 @@
@Override
public int getModelParameter(int modelHandle, int modelParam) {
- Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
- modelParam));
synchronized (SoundTriggerModule.this) {
checkValid();
return mLoadedModels.get(modelHandle).getParameter(modelParam);
@@ -372,8 +352,6 @@
@Override
@Nullable
public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
- Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
- modelParam));
synchronized (SoundTriggerModule.this) {
checkValid();
return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
@@ -584,8 +562,6 @@
public void recognitionCallback(
@NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
int cookie) {
- Log.d(TAG, String.format("recognitionCallback_2_1(event=%s, cookie=%d)",
- recognitionEvent, cookie));
synchronized (SoundTriggerModule.this) {
android.media.soundtrigger_middleware.RecognitionEvent aidlEvent =
ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
@@ -608,8 +584,6 @@
public void phraseRecognitionCallback(
@NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
int cookie) {
- Log.d(TAG, String.format("phraseRecognitionCallback_2_1(event=%s, cookie=%d)",
- phraseRecognitionEvent, cookie));
synchronized (SoundTriggerModule.this) {
android.media.soundtrigger_middleware.PhraseRecognitionEvent aidlEvent =
ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 2115f7c..6eb3c0f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1173,6 +1173,10 @@
}
};
+ private Runnable mTryToRebindRunnable = () -> {
+ tryToRebind();
+ };
+
WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper, int clientUid) {
mInfo = info;
mWallpaper = wallpaper;
@@ -1279,7 +1283,7 @@
saveSettingsLocked(mWallpaper.userId);
}
FgThread.getHandler().removeCallbacks(mResetRunnable);
- mContext.getMainThreadHandler().removeCallbacks(this::tryToRebind);
+ mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable);
}
}
}
@@ -1337,7 +1341,7 @@
< WALLPAPER_RECONNECT_TIMEOUT_MS) {
// Bind fail without timeout, schedule rebind
Slog.w(TAG, "Rebind fail! Try again later");
- mContext.getMainThreadHandler().postDelayed(this::tryToRebind, 1000);
+ mContext.getMainThreadHandler().postDelayed(mTryToRebindRunnable, 1000);
} else {
// Timeout
Slog.w(TAG, "Reverting to built-in wallpaper!");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index f52c7f2..3210304 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1819,12 +1819,12 @@
*/
private int deliverToCurrentTopIfNeeded(ActivityStack topStack) {
final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
- final boolean dontStart = top != null
+ final boolean dontStart = top != null && mStartActivity.resultTo == null
&& top.mActivityComponent.equals(mStartActivity.mActivityComponent)
&& top.mUserId == mStartActivity.mUserId
&& top.attachedToProcess()
&& ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
- || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
+ || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
// This allows home activity to automatically launch on secondary display when
// display added, if home was the top activity on default display, instead of
// sending new intent to the home activity on default display.
@@ -2055,6 +2055,8 @@
&& !isLaunchModeOneOf(LAUNCH_SINGLE_TASK, LAUNCH_SINGLE_INSTANCE)
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
+ sendNewTaskResultRequestIfNeeded();
+
if ((mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) {
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
@@ -2236,8 +2238,6 @@
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
}
-
- sendNewTaskResultRequestIfNeeded();
}
private void computeSourceStack() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 240f566..4ac809d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -527,9 +527,9 @@
/**
* Hide IME using imeTargetWindow when requested.
*
- * @param displayId on which IME is shown
+ * @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden.
*/
- public abstract void hideIme(int displayId);
+ public abstract void hideIme(IBinder imeTargetWindowToken);
/**
* Tell window manager about a package that should not be running with high refresh rate
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a59eab5..0169a4f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7441,27 +7441,28 @@
return;
}
imeTarget = imeTarget.getImeControlTarget();
-
- final int displayId = imeTarget.getDisplayId();
- mRoot.getDisplayContent(displayId).getInsetsStateController().getImeSourceProvider()
+ imeTarget.getDisplayContent().getInsetsStateController().getImeSourceProvider()
.scheduleShowImePostLayout(imeTarget);
}
}
@Override
- public void hideIme(int displayId) {
+ public void hideIme(IBinder imeTargetWindowToken) {
synchronized (mGlobalLock) {
- final DisplayContent dc = mRoot.getDisplayContent(displayId);
- if (dc != null) {
- InsetsControlTarget imeControlTarget = dc.mInputMethodControlTarget;
- if (imeControlTarget == null) {
- return;
- }
- // If there was a pending IME show(), reset it as IME has been
- // requested to be hidden.
- dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
- imeControlTarget.hideInsets(WindowInsets.Type.ime(), true /* fromIme */);
+ WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
+ if (imeTarget == null) {
+ // The target window no longer exists.
+ return;
}
+ final DisplayContent dc = imeTarget.getImeControlTarget().getDisplayContent();
+ // If there was a pending IME show(), reset it as IME has been
+ // requested to be hidden.
+ dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
+ if (dc.mInputMethodControlTarget == null) {
+ return;
+ }
+ dc.mInputMethodControlTarget.hideInsets(
+ WindowInsets.Type.ime(), true /* fromIme */);
}
}
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 5a8e25e4..05aa359 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -355,10 +355,11 @@
}
static jlong vibratorPerformEffect(JNIEnv* env, jclass, jlong effect, jlong strength,
- jobject vibration) {
+ jobject vibration, jboolean withCallback) {
if (auto hal = getHal<aidl::IVibrator>()) {
int32_t lengthMs;
- sp<AidlVibratorCallback> effectCallback = new AidlVibratorCallback(env, vibration);
+ sp<AidlVibratorCallback> effectCallback =
+ (withCallback != JNI_FALSE ? new AidlVibratorCallback(env, vibration) : nullptr);
aidl::Effect effectType(static_cast<aidl::Effect>(effect));
aidl::EffectStrength effectStrength(static_cast<aidl::EffectStrength>(strength));
@@ -478,24 +479,24 @@
}
static const JNINativeMethod method_table[] = {
- { "vibratorExists", "()Z", (void*)vibratorExists },
- { "vibratorInit", "()V", (void*)vibratorInit },
- { "vibratorOn", "(J)V", (void*)vibratorOn },
- { "vibratorOff", "()V", (void*)vibratorOff },
- { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
- { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
- { "vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;)J",
- (void*)vibratorPerformEffect},
- { "vibratorPerformComposedEffect",
- "([Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/VibratorService$Vibration;)V",
- (void*)vibratorPerformComposedEffect},
- { "vibratorGetSupportedEffects", "()[I",
- (void*)vibratorGetSupportedEffects},
- { "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
- { "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
- { "vibratorGetCapabilities", "()J", (void*)vibratorGetCapabilities},
- { "vibratorAlwaysOnEnable", "(JJJ)V", (void*)vibratorAlwaysOnEnable},
- { "vibratorAlwaysOnDisable", "(J)V", (void*)vibratorAlwaysOnDisable},
+ {"vibratorExists", "()Z", (void*)vibratorExists},
+ {"vibratorInit", "()V", (void*)vibratorInit},
+ {"vibratorOn", "(J)V", (void*)vibratorOn},
+ {"vibratorOff", "()V", (void*)vibratorOff},
+ {"vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
+ {"vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
+ {"vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;Z)J",
+ (void*)vibratorPerformEffect},
+ {"vibratorPerformComposedEffect",
+ "([Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/"
+ "VibratorService$Vibration;)V",
+ (void*)vibratorPerformComposedEffect},
+ {"vibratorGetSupportedEffects", "()[I", (void*)vibratorGetSupportedEffects},
+ {"vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
+ {"vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
+ {"vibratorGetCapabilities", "()J", (void*)vibratorGetCapabilities},
+ {"vibratorAlwaysOnEnable", "(JJJ)V", (void*)vibratorAlwaysOnEnable},
+ {"vibratorAlwaysOnDisable", "(J)V", (void*)vibratorAlwaysOnDisable},
};
int register_android_server_VibratorService(JNIEnv *env) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ff8c209..c1ac55f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -394,6 +394,7 @@
mStartCount = SystemProperties.getInt(SYSPROP_START_COUNT, 0) + 1;
mRuntimeStartElapsedTime = SystemClock.elapsedRealtime();
mRuntimeStartUptime = SystemClock.uptimeMillis();
+ Process.setStartTimes(mRuntimeStartElapsedTime, mRuntimeStartUptime);
// Remember if it's runtime restart(when sys.boot_completed is already set) or reboot
// We don't use "mStartCount > 1" here because it'll be wrong on a FDE device.
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index 407f67e..44f4ccf 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.app.compat.ChangeIdStateCache;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -74,6 +75,7 @@
// Assume userdebug/eng non-final build
when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
when(mBuildClassifier.isFinalBuild()).thenReturn(false);
+ ChangeIdStateCache.disable();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index 4a686ee..53b90f2 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -62,6 +62,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ android.app.compat.ChangeIdStateCache.disable();
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
new PackageManager.NameNotFoundException());
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index be873bd..d9101bf 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -60,6 +60,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
+import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -119,7 +120,6 @@
private static final String PLAY_STORE_PKG = "com.android.vending";
private static final String ADB_INSTALLER = "adb";
private static final String PLAY_STORE_CERT = "play_store_cert";
- private static final String ADB_CERT = "";
@org.junit.Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -137,11 +137,12 @@
@Mock
Handler mHandler;
+ private final Context mRealContext = InstrumentationRegistry.getTargetContext();
+
private PackageManager mSpyPackageManager;
private File mTestApk;
private File mTestApkTwoCerts;
- private final Context mRealContext = InstrumentationRegistry.getTargetContext();
// under test
private AppIntegrityManagerServiceImpl mService;
@@ -163,8 +164,7 @@
mPackageManagerInternal,
mRuleEvaluationEngine,
mIntegrityFileManager,
- mHandler,
- /* checkIntegrityForRuleProviders= */ true);
+ mHandler);
mSpyPackageManager = spy(mRealContext.getPackageManager());
// setup mocks to prevent NPE
@@ -172,6 +172,9 @@
when(mMockContext.getResources()).thenReturn(mMockResources);
when(mMockResources.getStringArray(anyInt())).thenReturn(new String[]{});
when(mIntegrityFileManager.initialized()).thenReturn(true);
+ // These are needed to override the Settings.Global.get result.
+ when(mMockContext.getContentResolver()).thenReturn(mRealContext.getContentResolver());
+ setIntegrityCheckIncludesRuleProvider(true);
}
@After
@@ -201,6 +204,7 @@
@Test
public void updateRuleSet_notSystemApp() throws Exception {
whitelistUsAsRuleProvider();
+ makeUsSystemApp(false);
Rule rule =
new Rule(
new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
@@ -411,14 +415,7 @@
public void verifierAsInstaller_skipIntegrityVerification() throws Exception {
whitelistUsAsRuleProvider();
makeUsSystemApp();
- mService =
- new AppIntegrityManagerServiceImpl(
- mMockContext,
- mPackageManagerInternal,
- mRuleEvaluationEngine,
- mIntegrityFileManager,
- mHandler,
- /* checkIntegrityForRuleProviders= */ false);
+ setIntegrityCheckIncludesRuleProvider(false);
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mMockContext, atLeastOnce())
@@ -460,12 +457,21 @@
}
private void makeUsSystemApp() throws Exception {
+ makeUsSystemApp(true);
+ }
+
+ private void makeUsSystemApp(boolean isSystemApp) throws Exception {
PackageInfo packageInfo =
mRealContext.getPackageManager().getPackageInfo(TEST_FRAMEWORK_PACKAGE, 0);
- packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ if (isSystemApp) {
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ } else {
+ packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
+ }
doReturn(packageInfo)
.when(mSpyPackageManager)
.getPackageInfo(eq(TEST_FRAMEWORK_PACKAGE), anyInt());
+ when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
}
private Intent makeVerificationIntent() throws Exception {
@@ -492,4 +498,13 @@
intent.putExtra(Intent.EXTRA_LONG_VERSION_CODE, VERSION_CODE);
return intent;
}
+
+ private void setIntegrityCheckIncludesRuleProvider(boolean shouldInclude) throws Exception {
+ int value = shouldInclude ? 1 : 0;
+ Settings.Global.putInt(mRealContext.getContentResolver(),
+ Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, value);
+ assertThat(Settings.Global.getInt(mRealContext.getContentResolver(),
+ Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, -1) == 1).isEqualTo(
+ shouldInclude);
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 3c0e0af..0b24dd2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -60,7 +60,6 @@
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.provider.Settings;
-import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionService;
@@ -684,9 +683,9 @@
}
@Override
- public void showSession(IVoiceInteractionService service, Bundle args, int flags) {
+ public void showSession(Bundle args, int flags) {
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
final long caller = Binder.clearCallingIdentity();
try {
@@ -928,12 +927,10 @@
}
//----------------- Model management APIs --------------------------------//
- // TODO: add check to only allow active voice interaction service or keyphrase enrollment
- // application to manage voice models
@Override
public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
- enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES);
+ enforceCallerAllowedToEnrollVoiceModel();
if (bcp47Locale == null) {
throw new IllegalArgumentException("Illegal argument(s) in getKeyphraseSoundModel");
@@ -950,7 +947,7 @@
@Override
public int updateKeyphraseSoundModel(KeyphraseSoundModel model) {
- enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES);
+ enforceCallerAllowedToEnrollVoiceModel();
if (model == null) {
throw new IllegalArgumentException("Model must not be null");
}
@@ -975,7 +972,7 @@
@Override
public int deleteKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
- enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES);
+ enforceCallerAllowedToEnrollVoiceModel();
if (bcp47Locale == null) {
throw new IllegalArgumentException(
@@ -1008,10 +1005,9 @@
//----------------- SoundTrigger APIs --------------------------------//
@Override
- public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
- String bcp47Locale) {
+ public boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale) {
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
}
if (bcp47Locale == null) {
@@ -1030,10 +1026,10 @@
}
@Nullable
- public KeyphraseMetadata getEnrolledKeyphraseMetadata(IVoiceInteractionService service,
- String keyphrase, String bcp47Locale) {
+ public KeyphraseMetadata getEnrolledKeyphraseMetadata(String keyphrase,
+ String bcp47Locale) {
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
}
if (bcp47Locale == null) {
@@ -1065,10 +1061,10 @@
}
@Override
- public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) {
+ public ModuleProperties getDspModuleProperties() {
// Allow the call if this is the current voice interaction service.
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
final long caller = Binder.clearCallingIdentity();
try {
@@ -1080,12 +1076,11 @@
}
@Override
- public int startRecognition(IVoiceInteractionService service, int keyphraseId,
- String bcp47Locale, IRecognitionStatusCallback callback,
- RecognitionConfig recognitionConfig) {
+ public int startRecognition(int keyphraseId, String bcp47Locale,
+ IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
// Allow the call if this is the current voice interaction service.
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
if (callback == null || recognitionConfig == null || bcp47Locale == null) {
throw new IllegalArgumentException("Illegal argument(s) in startRecognition");
@@ -1117,11 +1112,10 @@
}
@Override
- public int stopRecognition(IVoiceInteractionService service, int keyphraseId,
- IRecognitionStatusCallback callback) {
+ public int stopRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
// Allow the call if this is the current voice interaction service.
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
}
final long caller = Binder.clearCallingIdentity();
@@ -1133,11 +1127,10 @@
}
@Override
- public int setParameter(IVoiceInteractionService service, int keyphraseId,
- @ModelParams int modelParam, int value) {
+ public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
// Allow the call if this is the current voice interaction service.
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
}
final long caller = Binder.clearCallingIdentity();
@@ -1149,11 +1142,10 @@
}
@Override
- public int getParameter(IVoiceInteractionService service, int keyphraseId,
- @ModelParams int modelParam) {
+ public int getParameter(int keyphraseId, @ModelParams int modelParam) {
// Allow the call if this is the current voice interaction service.
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
}
final long caller = Binder.clearCallingIdentity();
@@ -1166,11 +1158,10 @@
@Override
@Nullable
- public ModelParamRange queryParameter(IVoiceInteractionService service,
- int keyphraseId, @ModelParams int modelParam) {
+ public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
// Allow the call if this is the current voice interaction service.
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
}
final long caller = Binder.clearCallingIdentity();
@@ -1400,9 +1391,9 @@
}
@Override
- public void setUiHints(IVoiceInteractionService service, Bundle hints) {
+ public void setUiHints(Bundle hints) {
synchronized (this) {
- enforceIsCurrentVoiceInteractionService(service);
+ enforceIsCurrentVoiceInteractionService();
final int size = mVoiceInteractionSessionListeners.beginBroadcast();
for (int i = 0; i < size; ++i) {
@@ -1425,14 +1416,32 @@
}
}
- private void enforceIsCurrentVoiceInteractionService(IVoiceInteractionService service) {
- if (mImpl == null || mImpl.mService == null
- || service.asBinder() != mImpl.mService.asBinder()) {
+ private void enforceIsCurrentVoiceInteractionService() {
+ if (!isCallerCurrentVoiceInteractionService()) {
throw new
SecurityException("Caller is not the current voice interaction service");
}
}
+ private void enforceCallerAllowedToEnrollVoiceModel() {
+ enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES);
+ if (!isCallerCurrentVoiceInteractionService()
+ && !isCallerTrustedEnrollmentApplication()) {
+ throw new SecurityException("Caller is required to be the current voice interaction"
+ + " service or a system enrollment application to enroll voice models");
+ }
+ }
+
+ private boolean isCallerCurrentVoiceInteractionService() {
+ return mImpl != null
+ && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid();
+ }
+
+ private boolean isCallerTrustedEnrollmentApplication() {
+ return mImpl.mEnrollmentApplicationInfo.isUidSupportedEnrollmentApplication(
+ Binder.getCallingUid());
+ }
+
private void setImplLocked(VoiceInteractionManagerServiceImpl impl) {
mImpl = impl;
mAtmInternal.notifyActiveVoiceInteractionServiceChanged(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index a62b03c..b813f87 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -36,6 +36,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -78,6 +79,7 @@
final IActivityManager mAm;
final IActivityTaskManager mAtm;
final VoiceInteractionServiceInfo mInfo;
+ final KeyphraseEnrollmentInfo mEnrollmentApplicationInfo;
final ComponentName mSessionComponentName;
final IWindowManager mIWindowManager;
boolean mBound = false;
@@ -133,6 +135,7 @@
mComponent = service;
mAm = ActivityManager.getService();
mAtm = ActivityTaskManager.getService();
+ mEnrollmentApplicationInfo = new KeyphraseEnrollmentInfo(context.getPackageManager());
VoiceInteractionServiceInfo info;
try {
info = new VoiceInteractionServiceInfo(context.getPackageManager(), service, mUser);
@@ -403,6 +406,7 @@
pw.println(" Active session:");
mActiveSession.dump(" ", pw);
}
+ pw.println(" " + mEnrollmentApplicationInfo.toString());
}
void startLocked() {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 5b6935b..bf886c2 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1766,9 +1766,12 @@
return 1;
}
- // First extract the Package name without modifying it (via --rename-manifest-package).
- if (Maybe<AppInfo> maybe_app_info =
+ // Determine the package name under which to merge resources.
+ if (options_.rename_resources_package) {
+ context_->SetCompilationPackage(options_.rename_resources_package.value());
+ } else if (Maybe<AppInfo> maybe_app_info =
ExtractAppInfoFromManifest(manifest_xml.get(), context_->GetDiagnostics())) {
+ // Extract the package name from the manifest ignoring the value of --rename-manifest-package.
const AppInfo& app_info = maybe_app_info.value();
context_->SetCompilationPackage(app_info.package);
}
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 4722358..e7be434 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -45,6 +45,7 @@
bool auto_add_overlay = false;
bool override_styles_instead_of_overlaying = false;
OutputFormat output_format = OutputFormat::kApk;
+ Maybe<std::string> rename_resources_package;
// Java/Proguard options.
Maybe<std::string> generate_java_class_path;
@@ -256,6 +257,8 @@
&options_.override_styles_instead_of_overlaying);
AddOptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml.",
&options_.manifest_fixer_options.rename_manifest_package);
+ AddOptionalFlag("--rename-resources-package", "Renames the package in resources table",
+ &options_.rename_resources_package);
AddOptionalFlag("--rename-instrumentation-target-package",
"Changes the name of the target package for instrumentation. Most useful\n"
"when used in conjunction with --rename-manifest-package.",