summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobScheduler.java6
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java21
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java53
-rw-r--r--apex/media/framework/java/android/media/MediaParser.java113
-rw-r--r--apex/statsd/Android.bp2
-rw-r--r--apex/statsd/aidl/Android.bp6
-rw-r--r--cmds/statsd/src/atoms.proto2
-rw-r--r--core/java/android/app/role/RoleManager.java1
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java3
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java36
-rw-r--r--core/java/android/net/IpSecManager.java2
-rw-r--r--core/java/android/view/RenderNodeAnimator.java10
-rw-r--r--core/java/android/webkit/UserPackage.java2
-rw-r--r--core/java/android/webkit/WebViewFactory.java2
-rw-r--r--core/proto/android/stats/mediametrics/mediametrics.proto2
-rw-r--r--media/jni/android_media_tv_Tuner.cpp277
-rw-r--r--packages/SystemUI/res/layout/bubble_overflow_activity.xml45
-rw-r--r--packages/SystemUI/res/values/strings.xml6
-rw-r--r--packages/SystemUI/src/com/android/systemui/SysUIToast.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java6
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java5
-rw-r--r--services/core/java/com/android/server/IpSecService.java18
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java353
-rw-r--r--services/core/java/com/android/server/audio/AudioServiceEvents.java33
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java7
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java472
-rw-r--r--services/core/java/com/android/server/connectivity/VpnIkev2Utils.java390
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java117
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java13
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java4
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLogger.java34
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java23
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java4
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java5
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaPolicy.java75
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java371
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java67
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java54
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java146
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java31
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java214
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java14
-rw-r--r--telecomm/java/android/telecom/Call.java23
-rw-r--r--telecomm/java/android/telecom/Conference.java7
-rw-r--r--telecomm/java/android/telecom/Connection.java20
-rw-r--r--telecomm/java/android/telecom/ConnectionService.java41
-rw-r--r--telecomm/java/android/telecom/InCallAdapter.java14
-rw-r--r--telecomm/java/android/telecom/InCallService.java37
-rw-r--r--telecomm/java/com/android/internal/telecom/IConnectionService.aidl3
-rw-r--r--telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl2
-rwxr-xr-xtelephony/java/android/telephony/CarrierConfigManager.java9
-rw-r--r--telephony/java/android/telephony/PreciseDisconnectCause.java331
-rw-r--r--tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java5
-rw-r--r--tests/net/java/com/android/server/connectivity/VpnTest.java4
-rw-r--r--wifi/java/android/net/wifi/hotspot2/pps/Credential.java11
74 files changed, 3078 insertions, 748 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 08b1c2b9f548..abf78c67fa6f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -56,6 +56,12 @@ import java.util.List;
* instantiate this class directly; instead, retrieve it through
* {@link android.content.Context#getSystemService
* Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)}.
+ *
+ * <p class="caution"><strong>Note:</strong> Beginning with API 30
+ * ({@link android.os.Build.VERSION_CODES#R}), JobScheduler will throttle runaway applications.
+ * Calling {@link #schedule(JobInfo)} and other such methods with very high frequency is indicative
+ * of an app bug and so, to make sure the system doesn't get overwhelmed, JobScheduler will begin
+ * to throttle apps that show buggy behavior, regardless of target SDK version.
*/
@SystemService(Context.JOB_SCHEDULER_SERVICE)
public abstract class JobScheduler {
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index d2d942a4a7e5..dc72d6d9c4b3 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -85,6 +85,7 @@ public interface AppStandbyInternal {
/**
* Checks if an app has been idle for a while and filters out apps that are excluded.
* It returns false if the current system state allows all apps to be considered active.
+ * This happens if the device is plugged in or otherwise temporarily allowed to make exceptions.
* Called by interface impls.
*/
boolean isAppIdleFiltered(String packageName, int appId, int userId,
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 b516279b58a5..e4c6b52f94bb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -37,8 +37,6 @@ import android.app.job.JobSnapshot;
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -57,7 +55,6 @@ import android.net.Uri;
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -90,7 +87,6 @@ import com.android.server.AppStateTracker;
import com.android.server.DeviceIdleInternal;
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.compat.PlatformCompat;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
import com.android.server.job.controllers.BackgroundJobsController;
@@ -155,16 +151,6 @@ public class JobSchedulerService extends com.android.server.SystemService
/** The maximum number of jobs that we allow an unprivileged app to schedule */
private static final int MAX_JOBS_PER_APP = 100;
- /**
- * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and
- * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app
- * calls the APIs too frequently.
- */
- @ChangeId
- // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up.
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
- protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L;
-
@VisibleForTesting
public static Clock sSystemClock = Clock.systemUTC();
@@ -264,7 +250,6 @@ public class JobSchedulerService extends com.android.server.SystemService
private final CountQuotaTracker mQuotaTracker;
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
- private final PlatformCompat mPlatformCompat;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -986,9 +971,7 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
mAppStandbyInternal.restrictApp(
pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY);
- if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
- && mPlatformCompat.isChangeEnabledByPackageName(
- CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+ if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) {
final boolean isDebuggable;
synchronized (mLock) {
if (!mDebuggableApps.containsKey(packageName)) {
@@ -1370,8 +1353,6 @@ public class JobSchedulerService extends com.android.server.SystemService
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- mPlatformCompat =
- (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER);
mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
mConstants.API_QUOTA_SCHEDULE_COUNT,
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index f1bfa0411978..e343478ec61f 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -48,6 +48,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import android.annotation.NonNull;
@@ -71,9 +72,8 @@ import android.content.pm.ParceledListSlice;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkRequest;
import android.net.NetworkScoreManager;
+import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Environment;
@@ -285,6 +285,7 @@ public class AppStandbyController implements AppStandbyInternal {
long mInitialForegroundServiceStartTimeoutMillis;
private volatile boolean mAppIdleEnabled;
+ private boolean mIsCharging;
private boolean mSystemServicesReady = false;
// There was a system update, defaults need to be initialized after services are ready
private boolean mPendingInitializeDefaults;
@@ -360,6 +361,11 @@ public class AppStandbyController implements AppStandbyInternal {
mHandler = new AppStandbyHandler(mInjector.getLooper());
mPackageManager = mContext.getPackageManager();
+ DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver();
+ IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
+ deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+ mContext.registerReceiver(deviceStateReceiver, deviceStates);
+
synchronized (mAppIdleLock) {
mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
mInjector.elapsedRealtime());
@@ -417,6 +423,8 @@ public class AppStandbyController implements AppStandbyInternal {
if (mPendingOneTimeCheckIdleStates) {
postOneTimeCheckIdleStates();
}
+ } else if (phase == PHASE_BOOT_COMPLETED) {
+ setChargingState(mInjector.isCharging());
}
}
@@ -515,6 +523,16 @@ public class AppStandbyController implements AppStandbyInternal {
appUsage.bucketingReason, false);
}
+ @VisibleForTesting
+ void setChargingState(boolean isCharging) {
+ synchronized (mAppIdleLock) {
+ if (mIsCharging != isCharging) {
+ if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
+ mIsCharging = isCharging;
+ }
+ }
+ }
+
@Override
public void postCheckIdleStates(int userId) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
@@ -977,6 +995,11 @@ public class AppStandbyController implements AppStandbyInternal {
if (isAppSpecial(packageName, appId, userId)) {
return false;
} else {
+ synchronized (mAppIdleLock) {
+ if (!mAppIdleEnabled || mIsCharging) {
+ return false;
+ }
+ }
return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
}
}
@@ -1543,6 +1566,8 @@ public class AppStandbyController implements AppStandbyInternal {
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
+ pw.print(" mIsCharging=");
+ pw.print(mIsCharging);
pw.println();
pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
@@ -1560,6 +1585,7 @@ public class AppStandbyController implements AppStandbyInternal {
private final Looper mLooper;
private IDeviceIdleController mDeviceIdleController;
private IBatteryStats mBatteryStats;
+ private BatteryManager mBatteryManager;
private PackageManagerInternal mPackageManagerInternal;
private DisplayManager mDisplayManager;
private PowerManager mPowerManager;
@@ -1593,6 +1619,7 @@ public class AppStandbyController implements AppStandbyInternal {
mDisplayManager = (DisplayManager) mContext.getSystemService(
Context.DISPLAY_SERVICE);
mPowerManager = mContext.getSystemService(PowerManager.class);
+ mBatteryManager = mContext.getSystemService(BatteryManager.class);
final ActivityManager activityManager =
(ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -1630,6 +1657,10 @@ public class AppStandbyController implements AppStandbyInternal {
return buildFlag && runtimeFlag;
}
+ boolean isCharging() {
+ return mBatteryManager.isCharging();
+ }
+
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
}
@@ -1766,15 +1797,19 @@ public class AppStandbyController implements AppStandbyInternal {
}
};
- private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder().build();
-
- private final ConnectivityManager.NetworkCallback mNetworkCallback
- = new ConnectivityManager.NetworkCallback() {
+ private class DeviceStateReceiver extends BroadcastReceiver {
@Override
- public void onAvailable(Network network) {
- mConnectivityManager.unregisterNetworkCallback(this);
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case BatteryManager.ACTION_CHARGING:
+ setChargingState(true);
+ break;
+ case BatteryManager.ACTION_DISCHARGING:
+ setChargingState(false);
+ break;
+ }
}
- };
+ }
private final DisplayManager.DisplayListener mDisplayListener
= new DisplayManager.DisplayListener() {
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 3eed26b108f4..7d18578aff66 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -50,6 +50,7 @@ import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.ParsableByteArray;
+import com.google.android.exoplayer2.video.ColorInfo;
import java.io.EOFException;
import java.io.IOException;
@@ -810,19 +811,17 @@ public final class MediaParser {
// Private static methods.
private static MediaFormat toMediaFormat(Format format) {
-
MediaFormat result = new MediaFormat();
- if (format.bitrate != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
- }
- if (format.channelCount != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
- }
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
+
+ ColorInfo colorInfo = format.colorInfo;
+ if (colorInfo != null) {
+ setOptionalMediaFormatInt(
+ result, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace);
- if (format.colorInfo != null) {
- result.setInteger(MediaFormat.KEY_COLOR_TRANSFER, format.colorInfo.colorTransfer);
- result.setInteger(MediaFormat.KEY_COLOR_RANGE, format.colorInfo.colorRange);
- result.setInteger(MediaFormat.KEY_COLOR_STANDARD, format.colorInfo.colorSpace);
if (format.colorInfo.hdrStaticInfo != null) {
result.setByteBuffer(
MediaFormat.KEY_HDR_STATIC_INFO,
@@ -830,63 +829,50 @@ public final class MediaParser {
}
}
- if (format.sampleMimeType != null) {
- result.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
- }
- if (format.codecs != null) {
- result.setString(MediaFormat.KEY_CODECS_STRING, format.codecs);
- }
+ setOptionalMediaFormatString(result, MediaFormat.KEY_MIME, format.sampleMimeType);
+ setOptionalMediaFormatString(result, MediaFormat.KEY_CODECS_STRING, format.codecs);
if (format.frameRate != Format.NO_VALUE) {
result.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate);
}
- if (format.width != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_WIDTH, format.width);
- }
- if (format.height != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_HEIGHT, format.height);
- }
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_WIDTH, format.width);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_HEIGHT, format.height);
+
List<byte[]> initData = format.initializationData;
if (initData != null) {
for (int i = 0; i < initData.size(); i++) {
result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i)));
}
}
- if (format.language != null) {
- result.setString(MediaFormat.KEY_LANGUAGE, format.language);
- }
- if (format.maxInputSize != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
- }
- if (format.pcmEncoding != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
- }
- if (format.rotationDegrees != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_ROTATION, format.rotationDegrees);
- }
- if (format.sampleRate != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
- }
+ setOptionalMediaFormatString(result, MediaFormat.KEY_LANGUAGE, format.language);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_ROTATION, format.rotationDegrees);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
+
int selectionFlags = format.selectionFlags;
- if ((selectionFlags & C.SELECTION_FLAG_AUTOSELECT) != 0) {
- result.setInteger(MediaFormat.KEY_IS_AUTOSELECT, 1);
- }
- if ((selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0) {
- result.setInteger(MediaFormat.KEY_IS_DEFAULT, 1);
- }
- if ((selectionFlags & C.SELECTION_FLAG_FORCED) != 0) {
- result.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 1);
- }
- if (format.encoderDelay != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
- }
- if (format.encoderPadding != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
+ result.setInteger(
+ MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT);
+ result.setInteger(MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT);
+ result.setInteger(
+ MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED);
+
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
+
+ if (format.pixelWidthHeightRatio != Format.NO_VALUE && format.pixelWidthHeightRatio != 0) {
+ int parWidth = 1;
+ int parHeight = 1;
+ if (format.pixelWidthHeightRatio < 1.0f) {
+ parHeight = 1 << 30;
+ parWidth = (int) (format.pixelWidthHeightRatio * parHeight);
+ } else if (format.pixelWidthHeightRatio > 1.0f) {
+ parWidth = 1 << 30;
+ parHeight = (int) (parWidth / format.pixelWidthHeightRatio);
+ }
+ result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, parWidth);
+ result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, parHeight);
+ result.setFloat("pixel-width-height-ratio-float", format.pixelWidthHeightRatio);
}
- // TODO: Implement float to fraction conversion.
- // if (format.pixelWidthHeightRatio != Format.NO_VALUE) {
- // result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, );
- // result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, );
- // }
// LACK OF SUPPORT FOR:
// format.accessibilityChannel;
@@ -899,6 +885,19 @@ public final class MediaParser {
return result;
}
+ private static void setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value) {
+ if (value != Format.NO_VALUE) {
+ mediaFormat.setInteger(key, value);
+ }
+ }
+
+ private static void setOptionalMediaFormatString(
+ MediaFormat mediaFormat, String key, @Nullable String value) {
+ if (value != null) {
+ mediaFormat.setString(key, value);
+ }
+ }
+
private static DrmInitData toFrameworkDrmInitData(
com.google.android.exoplayer2.drm.DrmInitData drmInitData) {
// TODO: Implement.
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index 1f9f18cd051a..c0f84a0ba070 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -20,6 +20,7 @@ apex {
apex_defaults {
native_shared_libs: [
+ "libstatspull",
"libstats_jni",
],
// binaries: ["vold"],
@@ -28,6 +29,7 @@ apex_defaults {
"service-statsd",
],
// prebuilts: ["my_prebuilt"],
+ compile_multilib: "both",
name: "com.android.os.statsd-defaults",
key: "com.android.os.statsd.key",
certificate: ":com.android.os.statsd.certificate",
diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp
index 7c93bc73e45d..4ccdd7e734db 100644
--- a/apex/statsd/aidl/Android.bp
+++ b/apex/statsd/aidl/Android.bp
@@ -38,6 +38,10 @@ aidl_interface {
},
ndk: {
enabled: true,
+ apex_available: [
+ "com.android.os.statsd",
+ ],
}
- }
+
+ },
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 03f97d80824d..23a4437910f7 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -381,7 +381,7 @@ message Atom {
UserspaceRebootReported userspace_reboot_reported = 243 [(module) = "framework"];
NotificationReported notification_reported = 244 [(module) = "framework"];
NotificationPanelReported notification_panel_reported = 245;
- NotificationChannelModified notification_panel_modified = 246;
+ NotificationChannelModified notification_channel_modified = 246;
IntegrityCheckResultReported integrity_check_result_reported = 247 [(module) = "framework"];
IntegrityRulesPushed integrity_rules_pushed = 248 [(module) = "framework"];
CellBroadcastMessageReported cb_message_reported =
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index ea66fd475153..db4f1de1f743 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -90,6 +90,7 @@ public final class RoleManager {
* The name of the dialer role.
*
* @see Intent#ACTION_DIAL
+ * @see android.telecom.InCallService
*/
public static final String ROLE_DIALER = "android.app.role.DIALER";
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 5668944dfd4e..2c701b48455c 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -599,7 +599,8 @@ public final class UsageStatsManager {
/**
* Returns whether the specified app is currently considered inactive. This will be true if the
* app hasn't been used directly or indirectly for a period of time defined by the system. This
- * could be of the order of several hours or days.
+ * could be of the order of several hours or days. Apps are not considered inactive when the
+ * device is charging.
* @param packageName The package name of the app to query
* @return whether the app is currently considered inactive
*/
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index ef28e6c6db2a..ac36188ee61a 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -75,6 +75,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
/**
* @hide
*/
+ public static final String KEY_DEVICE_CREDENTIAL_TITLE = "device_credential_title";
+ /**
+ * @hide
+ */
+ public static final String KEY_DEVICE_CREDENTIAL_SUBTITLE = "device_credential_subtitle";
+ /**
+ * @hide
+ */
+ public static final String KEY_DEVICE_CREDENTIAL_DESCRIPTION = "device_credential_description";
+ /**
+ * @hide
+ */
public static final String KEY_NEGATIVE_TEXT = "negative_text";
/**
* @hide
@@ -221,6 +233,30 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
/**
+ * Sets an optional title, subtitle, and/or description that will override other text when
+ * the user is authenticating with PIN/pattern/password. Currently for internal use only.
+ * @return This builder.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ @NonNull
+ public Builder setTextForDeviceCredential(
+ @Nullable CharSequence title,
+ @Nullable CharSequence subtitle,
+ @Nullable CharSequence description) {
+ if (title != null) {
+ mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_TITLE, title);
+ }
+ if (subtitle != null) {
+ mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_SUBTITLE, subtitle);
+ }
+ if (description != null) {
+ mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_DESCRIPTION, description);
+ }
+ return this;
+ }
+
+ /**
* Required: Sets the text, executor, and click listener for the negative button on the
* prompt. This is typically a cancel button, but may be also used to show an alternative
* method for authentication, such as a screen that asks for a backup password.
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 09ec6c35fcb9..d83715c692f7 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -51,7 +51,7 @@ import java.net.Socket;
*
* <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
* transport mode security associations and apply them to individual sockets. Applications looking
- * to create a VPN should use {@link VpnService}.
+ * to create an IPsec VPN should use {@link VpnManager} and {@link Ikev2VpnProfile}.
*
* @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
* Internet Protocol</a>
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 06cb51927ba8..0c1edac6608c 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -19,7 +19,6 @@ package android.view;
import android.animation.Animator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
@@ -109,12 +108,10 @@ public class RenderNodeAnimator extends Animator {
private long mStartDelay = 0;
private long mStartTime;
- @UnsupportedAppUsage
public static int mapViewPropertyToRenderProperty(int viewProperty) {
return sViewPropertyAnimatorMap.get(viewProperty);
}
- @UnsupportedAppUsage
public RenderNodeAnimator(int property, float finalValue) {
mRenderProperty = property;
mFinalValue = finalValue;
@@ -122,7 +119,6 @@ public class RenderNodeAnimator extends Animator {
init(nCreateAnimator(property, finalValue));
}
- @UnsupportedAppUsage
public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
init(nCreateCanvasPropertyFloatAnimator(
property.getNativeContainer(), finalValue));
@@ -137,7 +133,6 @@ public class RenderNodeAnimator extends Animator {
* {@link #PAINT_STROKE_WIDTH}
* @param finalValue The target value for the property
*/
- @UnsupportedAppUsage
public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
init(nCreateCanvasPropertyPaintAnimator(
property.getNativeContainer(), paintField, finalValue));
@@ -289,7 +284,6 @@ public class RenderNodeAnimator extends Animator {
}
/** @hide */
- @UnsupportedAppUsage
public void setTarget(View view) {
mViewTarget = view;
setTarget(mViewTarget.mRenderNode);
@@ -301,7 +295,6 @@ public class RenderNodeAnimator extends Animator {
}
/** @hide */
- @UnsupportedAppUsage
public void setTarget(DisplayListCanvas canvas) {
setTarget((RecordingCanvas) canvas);
}
@@ -316,7 +309,6 @@ public class RenderNodeAnimator extends Animator {
mTarget.addAnimator(this);
}
- @UnsupportedAppUsage
public void setStartValue(float startValue) {
checkMutable();
nSetStartValue(mNativePtr.get(), startValue);
@@ -337,7 +329,6 @@ public class RenderNodeAnimator extends Animator {
return mUnscaledStartDelay;
}
- @UnsupportedAppUsage
@Override
public RenderNodeAnimator setDuration(long duration) {
checkMutable();
@@ -502,7 +493,6 @@ public class RenderNodeAnimator extends Animator {
}
// Called by native
- @UnsupportedAppUsage
private static void callOnFinished(RenderNodeAnimator animator) {
if (animator.mHandler != null) {
animator.mHandler.post(animator::onFinished);
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 8a1a0b5ef9b0..556b24c94b36 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,7 +34,7 @@ public class UserPackage {
private final UserInfo mUserInfo;
private final PackageInfo mPackageInfo;
- public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.Q;
+ public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.R;
public UserPackage(UserInfo user, PackageInfo packageInfo) {
this.mUserInfo = user;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 941af6ef1d7a..8790bbdcd8f7 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -47,7 +47,7 @@ public final class WebViewFactory {
// visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
/** @hide */
private static final String CHROMIUM_WEBVIEW_FACTORY =
- "com.android.webview.chromium.WebViewChromiumFactoryProviderForQ";
+ "com.android.webview.chromium.WebViewChromiumFactoryProviderForR";
private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
diff --git a/core/proto/android/stats/mediametrics/mediametrics.proto b/core/proto/android/stats/mediametrics/mediametrics.proto
index 34ed90a8c90c..e1af9622adb3 100644
--- a/core/proto/android/stats/mediametrics/mediametrics.proto
+++ b/core/proto/android/stats/mediametrics/mediametrics.proto
@@ -154,6 +154,8 @@ message CodecData {
optional int64 latency_avg = 18;
optional int64 latency_count = 19;
optional int64 latency_unknown = 20;
+ optional int32 queue_input_buffer_error = 21;
+ optional int32 queue_secure_input_buffer_error = 22;
}
/**
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 08c3f988e85e..44f9b7c6eb39 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -27,16 +27,34 @@
#pragma GCC diagnostic ignored "-Wunused-function"
using ::android::hardware::Void;
+using ::android::hardware::hidl_bitfield;
using ::android::hardware::hidl_vec;
using ::android::hardware::tv::tuner::V1_0::DataFormat;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterAvSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterDownloadSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionBits;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType;
using ::android::hardware::tv::tuner::V1_0::DemuxMmtpPid;
using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits;
+using ::android::hardware::tv::tuner::V1_0::DemuxRecordScIndexType;
+using ::android::hardware::tv::tuner::V1_0::DemuxScHevcIndex;
+using ::android::hardware::tv::tuner::V1_0::DemuxScIndex;
+using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterType;
using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxTsIndex;
using ::android::hardware::tv::tuner::V1_0::DvrSettings;
using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSettings;
using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSifStandard;
@@ -1367,10 +1385,133 @@ static jobject android_media_tv_Tuner_open_time_filter(JNIEnv, jobject) {
return NULL;
}
-static DemuxFilterSettings getFilterSettings(
+static DemuxFilterSectionBits getFilterSectionBits(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithSectionBits");
+ jbyteArray jfilterBytes = static_cast<jbyteArray>(
+ env->GetObjectField(settings, env->GetFieldID(clazz, "mFilter", "[B")));
+ jsize size = env->GetArrayLength(jfilterBytes);
+ std::vector<uint8_t> filterBytes(size);
+ env->GetByteArrayRegion(
+ jfilterBytes, 0, size, reinterpret_cast<jbyte*>(&filterBytes[0]));
+
+ jbyteArray jmask = static_cast<jbyteArray>(
+ env->GetObjectField(settings, env->GetFieldID(clazz, "mMask", "[B")));
+ size = env->GetArrayLength(jmask);
+ std::vector<uint8_t> mask(size);
+ env->GetByteArrayRegion(jmask, 0, size, reinterpret_cast<jbyte*>(&mask[0]));
+
+ jbyteArray jmode = static_cast<jbyteArray>(
+ env->GetObjectField(settings, env->GetFieldID(clazz, "mMode", "[B")));
+ size = env->GetArrayLength(jmode);
+ std::vector<uint8_t> mode(size);
+ env->GetByteArrayRegion(jmode, 0, size, reinterpret_cast<jbyte*>(&mode[0]));
+
+ DemuxFilterSectionBits filterSectionBits {
+ .filter = filterBytes,
+ .mask = mask,
+ .mode = mode,
+ };
+ return filterSectionBits;
+}
+
+static DemuxFilterSectionSettings::Condition::TableInfo getFilterTableInfo(
+ JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithTableInfo");
+ uint16_t tableId = static_cast<uint16_t>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mTableId", "I")));
+ uint16_t version = static_cast<uint16_t>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mVersion", "I")));
+ DemuxFilterSectionSettings::Condition::TableInfo tableInfo {
+ .tableId = tableId,
+ .version = version,
+ };
+ return tableInfo;
+}
+
+static DemuxFilterSectionSettings getFilterSectionSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettings");
+ bool isCheckCrc = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mCrcEnabled", "Z")));
+ bool isRepeat = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRepeat", "Z")));
+ bool isRaw = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRaw", "Z")));
+
+ DemuxFilterSectionSettings filterSectionSettings {
+ .isCheckCrc = isCheckCrc,
+ .isRepeat = isRepeat,
+ .isRaw = isRaw,
+ };
+ if (env->IsInstanceOf(
+ settings,
+ env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithSectionBits"))) {
+ filterSectionSettings.condition.sectionBits(getFilterSectionBits(env, settings));
+ } else if (env->IsInstanceOf(
+ settings,
+ env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithTableInfo"))) {
+ filterSectionSettings.condition.tableInfo(getFilterTableInfo(env, settings));
+ }
+ return filterSectionSettings;
+}
+
+static DemuxFilterAvSettings getFilterAvSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/AvSettings");
+ bool isPassthrough = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsPassthrough", "Z")));
+ DemuxFilterAvSettings filterAvSettings {
+ .isPassthrough = isPassthrough,
+ };
+ return filterAvSettings;
+}
+
+static DemuxFilterPesDataSettings getFilterPesDataSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/PesSettings");
+ uint16_t streamId = static_cast<uint16_t>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mStreamId", "I")));
+ bool isRaw = static_cast<bool>(
+ env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRaw", "Z")));
+ DemuxFilterPesDataSettings filterPesDataSettings {
+ .streamId = streamId,
+ .isRaw = isRaw,
+ };
+ return filterPesDataSettings;
+}
+
+static DemuxFilterRecordSettings getFilterRecordSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/RecordSettings");
+ hidl_bitfield<DemuxTsIndex> tsIndexMask = static_cast<hidl_bitfield<DemuxTsIndex>>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mTsIndexMask", "I")));
+ DemuxRecordScIndexType scIndexType = static_cast<DemuxRecordScIndexType>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexType", "I")));
+ jint scIndexMask = env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexMask", "I"));
+
+ DemuxFilterRecordSettings filterRecordSettings {
+ .tsIndexMask = tsIndexMask,
+ .scIndexType = scIndexType,
+ };
+ if (scIndexType == DemuxRecordScIndexType::SC) {
+ filterRecordSettings.scIndexMask.sc(static_cast<hidl_bitfield<DemuxScIndex>>(scIndexMask));
+ } else if (scIndexType == DemuxRecordScIndexType::SC_HEVC) {
+ filterRecordSettings.scIndexMask.scHevc(
+ static_cast<hidl_bitfield<DemuxScHevcIndex>>(scIndexMask));
+ }
+ return filterRecordSettings;
+}
+
+static DemuxFilterDownloadSettings getFilterDownloadSettings(JNIEnv *env, const jobject& settings) {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/DownloadSettings");
+ uint32_t downloadId = static_cast<uint32_t>(
+ env->GetIntField(settings, env->GetFieldID(clazz, "mDownloadId", "I")));
+
+ DemuxFilterDownloadSettings filterDownloadSettings {
+ .downloadId = downloadId,
+ };
+ return filterDownloadSettings;
+}
+
+static DemuxFilterSettings getFilterConfiguration(
JNIEnv *env, int type, int subtype, jobject filterSettingsObj) {
DemuxFilterSettings filterSettings;
- // TODO: more setting types
jobject settingsObj =
env->GetObjectField(
filterSettingsObj,
@@ -1378,27 +1519,121 @@ static DemuxFilterSettings getFilterSettings(
env->FindClass("android/media/tv/tuner/filter/FilterConfiguration"),
"mSettings",
"Landroid/media/tv/tuner/filter/Settings;"));
- if (type == (int)DemuxFilterMainType::TS) {
- // DemuxTsFilterSettings
- jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration");
- int tpid = env->GetIntField(filterSettingsObj, env->GetFieldID(clazz, "mTpid", "I"));
- if (subtype == (int)DemuxTsFilterType::PES) {
- // DemuxFilterPesDataSettings
- jclass settingClazz =
- env->FindClass("android/media/tv/tuner/filter/PesSettings");
- int streamId = env->GetIntField(
- settingsObj, env->GetFieldID(settingClazz, "mStreamId", "I"));
- bool isRaw = (bool)env->GetBooleanField(
- settingsObj, env->GetFieldID(settingClazz, "mIsRaw", "Z"));
- DemuxFilterPesDataSettings filterPesDataSettings {
- .streamId = static_cast<uint16_t>(streamId),
- .isRaw = isRaw,
- };
+ DemuxFilterMainType mainType = static_cast<DemuxFilterMainType>(type);
+ switch (mainType) {
+ case DemuxFilterMainType::TS: {
+ jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration");
+ int tpid = env->GetIntField(filterSettingsObj, env->GetFieldID(clazz, "mTpid", "I"));
DemuxTsFilterSettings tsFilterSettings {
- .tpid = static_cast<uint16_t>(tpid),
+ .tpid = static_cast<uint16_t>(tpid),
};
- tsFilterSettings.filterSettings.pesData(filterPesDataSettings);
+
+ DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype);
+ switch (tsType) {
+ case DemuxTsFilterType::SECTION:
+ tsFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ case DemuxTsFilterType::AUDIO:
+ case DemuxTsFilterType::VIDEO:
+ tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj));
+ break;
+ case DemuxTsFilterType::PES:
+ tsFilterSettings.filterSettings.pesData(
+ getFilterPesDataSettings(env, settingsObj));
+ break;
+ case DemuxTsFilterType::RECORD:
+ tsFilterSettings.filterSettings.record(
+ getFilterRecordSettings(env, settingsObj));
+ break;
+ default:
+ break;
+ }
filterSettings.ts(tsFilterSettings);
+ break;
+ }
+ case DemuxFilterMainType::MMTP: {
+ DemuxMmtpFilterSettings mmtpFilterSettings;
+ DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype);
+ switch (mmtpType) {
+ case DemuxMmtpFilterType::SECTION:
+ mmtpFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ case DemuxMmtpFilterType::AUDIO:
+ case DemuxMmtpFilterType::VIDEO:
+ mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj));
+ break;
+ case DemuxMmtpFilterType::PES:
+ mmtpFilterSettings.filterSettings.pesData(
+ getFilterPesDataSettings(env, settingsObj));
+ break;
+ case DemuxMmtpFilterType::RECORD:
+ mmtpFilterSettings.filterSettings.record(
+ getFilterRecordSettings(env, settingsObj));
+ break;
+ case DemuxMmtpFilterType::DOWNLOAD:
+ mmtpFilterSettings.filterSettings.download(
+ getFilterDownloadSettings(env, settingsObj));
+ break;
+ default:
+ break;
+ }
+ filterSettings.mmtp(mmtpFilterSettings);
+ break;
+ }
+ case DemuxFilterMainType::IP: {
+ DemuxIpFilterSettings ipFilterSettings;
+ DemuxIpFilterType ipType = static_cast<DemuxIpFilterType>(subtype);
+ switch (ipType) {
+ case DemuxIpFilterType::SECTION:
+ ipFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ case DemuxIpFilterType::IP:
+ // TODO: handle passthrough
+ ipFilterSettings.filterSettings.bPassthrough(false);
+ break;
+ default:
+ break;
+ }
+ filterSettings.ip(ipFilterSettings);
+ break;
+ }
+ case DemuxFilterMainType::TLV: {
+ DemuxTlvFilterSettings tlvFilterSettings;
+ DemuxTlvFilterType tlvType = static_cast<DemuxTlvFilterType>(subtype);
+ switch (tlvType) {
+ case DemuxTlvFilterType::SECTION:
+ tlvFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ case DemuxTlvFilterType::TLV:
+ // TODO: handle passthrough
+ tlvFilterSettings.filterSettings.bPassthrough(false);
+ break;
+ default:
+ break;
+ }
+ filterSettings.tlv(tlvFilterSettings);
+ break;
+ }
+ case DemuxFilterMainType::ALP: {
+ DemuxAlpFilterSettings alpFilterSettings;
+ DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype);
+ switch (alpType) {
+ case DemuxAlpFilterType::SECTION:
+ alpFilterSettings.filterSettings.section(
+ getFilterSectionSettings(env, settingsObj));
+ break;
+ default:
+ break;
+ }
+ filterSettings.alp(alpFilterSettings);
+ break;
+ }
+ default: {
+ break;
}
}
return filterSettings;
@@ -1439,7 +1674,7 @@ static int android_media_tv_Tuner_configure_filter(
ALOGD("Failed to configure filter: filter not found");
return (int)Result::INVALID_STATE;
}
- DemuxFilterSettings filterSettings = getFilterSettings(env, type, subtype, settings);
+ DemuxFilterSettings filterSettings = getFilterConfiguration(env, type, subtype, settings);
Result res = iFilterSp->configure(filterSettings);
MQDescriptorSync<uint8_t> filterMQDesc;
if (res == Result::SUCCESS && filterSp->mFilterMQ == NULL) {
diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
index 95f205a1be34..481c4dbe3bf1 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_activity.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
@@ -14,8 +14,45 @@
~ limitations under the License
-->
-<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/bubble_overflow_recycler"
- android:layout_gravity="center_horizontal"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/bubble_overflow_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_gravity="center_horizontal">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/bubble_overflow_recycler"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:id="@+id/bubble_overflow_empty_state"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <TextView
+ android:id="@+id/bubble_overflow_empty_title"
+ android:text="@string/bubble_overflow_empty_title"
+ android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+ android:textColor="?android:attr/textColorSecondary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"/>
+
+ <TextView
+ android:id="@+id/bubble_overflow_empty_subtitle"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/bubble_overflow_empty_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6dd89d88d252..ef9e705d0a81 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1780,6 +1780,12 @@
<!-- [CHAR LIMIT=150] Notification Importance title: bubble level summary -->
<string name="notification_channel_summary_bubble">Keeps your attention with a floating shortcut to this content.</string>
+ <!-- [CHAR LIMIT=NONE] Empty overflow title -->
+ <string name="bubble_overflow_empty_title">No recent bubbles</string>
+
+ <!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
+ <string name="bubble_overflow_empty_subtitle">Recently dismissed bubbles will appear here for easy retrieval.</string>
+
<!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
<string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 0f7f1bebae57..023b74b9c07e 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -19,7 +19,6 @@ import static android.widget.Toast.Duration;
import android.annotation.StringRes;
import android.content.Context;
-import android.view.WindowManager;
import android.widget.Toast;
public class SysUIToast {
@@ -29,10 +28,7 @@ public class SysUIToast {
}
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
- Toast toast = Toast.makeText(context, text, duration);
- toast.getWindowParams().privateFlags |=
- WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- return toast;
+ return Toast.makeText(context, text, duration);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 68b05e358786..9de10406a822 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricPrompt;
@@ -33,6 +34,8 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -126,18 +129,18 @@ public abstract class AuthCredentialView extends LinearLayout {
mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS);
}
- private void setTextOrHide(TextView view, String string) {
- if (TextUtils.isEmpty(string)) {
+ private void setTextOrHide(TextView view, CharSequence text) {
+ if (TextUtils.isEmpty(text)) {
view.setVisibility(View.GONE);
} else {
- view.setText(string);
+ view.setText(text);
}
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
}
- private void setText(TextView view, String string) {
- view.setText(string);
+ private void setText(TextView view, CharSequence text) {
+ view.setText(text);
}
void setEffectiveUserId(int effectiveUserId) {
@@ -173,11 +176,9 @@ public abstract class AuthCredentialView extends LinearLayout {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- setText(mTitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_TITLE));
- setTextOrHide(mSubtitleView,
- mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE));
- setTextOrHide(mDescriptionView,
- mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION));
+ setText(mTitleView, getTitle(mBiometricPromptBundle));
+ setTextOrHide(mSubtitleView, getSubtitle(mBiometricPromptBundle));
+ setTextOrHide(mDescriptionView, getDescription(mBiometricPromptBundle));
final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId);
final Drawable image;
@@ -279,4 +280,28 @@ public abstract class AuthCredentialView extends LinearLayout {
}
}
}
+
+ @Nullable
+ private static CharSequence getTitle(@NonNull Bundle bundle) {
+ final CharSequence credentialTitle =
+ bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE);
+ return credentialTitle != null ? credentialTitle
+ : bundle.getCharSequence(BiometricPrompt.KEY_TITLE);
+ }
+
+ @Nullable
+ private static CharSequence getSubtitle(@NonNull Bundle bundle) {
+ final CharSequence credentialSubtitle =
+ bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE);
+ return credentialSubtitle != null ? credentialSubtitle
+ : bundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
+ }
+
+ @Nullable
+ private static CharSequence getDescription(@NonNull Bundle bundle) {
+ final CharSequence credentialDescription =
+ bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION);
+ return credentialDescription != null ? credentialDescription
+ : bundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 45705b76f09c..1e39954ed414 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -226,6 +226,10 @@ class Bubble {
mIconView.update(this);
}
+ void setInflated(boolean inflated) {
+ mInflated = inflated;
+ }
+
/**
* Set visibility of bubble in the expanded state.
*
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 05838abe184a..762e5f21cd63 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -749,7 +749,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
void promoteBubbleFromOverflow(Bubble bubble) {
- mBubbleData.promoteBubbleFromOverflow(bubble);
+ bubble.setInflateSynchronously(mInflateSynchronously);
+ mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 673121f92716..8a5aad875979 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -199,16 +199,21 @@ public class BubbleData {
dispatchPendingChanges();
}
- public void promoteBubbleFromOverflow(Bubble bubble) {
+ public void promoteBubbleFromOverflow(Bubble bubble, BubbleStackView stack,
+ BubbleIconFactory factory) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "promoteBubbleFromOverflow: " + bubble);
}
- mOverflowBubbles.remove(bubble);
- doAdd(bubble);
- setSelectedBubbleInternal(bubble);
+
// Preserve new order for next repack, which sorts by last updated time.
bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
- trim();
+ setSelectedBubbleInternal(bubble);
+ mOverflowBubbles.remove(bubble);
+
+ bubble.inflate(
+ b -> notificationEntryUpdated(bubble, /* suppressFlyout */
+ false, /* showInShade */ true),
+ mContext, stack, factory);
dispatchPendingChanges();
}
@@ -445,6 +450,10 @@ public class BubbleData {
mOverflowBubbles.add(0, bubbleToRemove);
if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
// Remove oldest bubble.
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "Overflow full. Remove bubble: " + mOverflowBubbles.get(
+ mOverflowBubbles.size() - 1));
+ }
mOverflowBubbles.remove(mOverflowBubbles.size() - 1);
}
}
@@ -511,7 +520,7 @@ public class BubbleData {
if (Objects.equals(bubble, mSelectedBubble)) {
return;
}
- if (bubble != null && !mBubbles.contains(bubble)) {
+ if (bubble != null && !mBubbles.contains(bubble) && !mOverflowBubbles.contains(bubble)) {
Log.e(TAG, "Cannot select bubble which doesn't exist!"
+ " (" + bubble + ") bubbles=" + mBubbles);
return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 0d5261dcb7f3..fe191f40d31f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -294,7 +294,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
ta.recycle();
mPointerDrawable.setTint(bgColor);
- if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(mContext.getResources())) {
+ if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+ mContext.getResources())) {
mActivityView.setCornerRadius(cornerRadius);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 2d55a1ddf654..f3cfa834dfc1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -26,7 +26,10 @@ import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -46,6 +49,7 @@ import javax.inject.Inject;
public class BubbleOverflowActivity extends Activity {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
+ private LinearLayout mEmptyState;
private BubbleController mBubbleController;
private BubbleOverflowAdapter mAdapter;
private RecyclerView mRecyclerView;
@@ -64,6 +68,7 @@ public class BubbleOverflowActivity extends Activity {
setBackgroundColor();
mMaxBubbles = getResources().getInteger(R.integer.bubbles_max_rendered);
+ mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
mRecyclerView.setLayoutManager(
new GridLayoutManager(getApplicationContext(),
@@ -73,9 +78,9 @@ public class BubbleOverflowActivity extends Activity {
mBubbleController::promoteBubbleFromOverflow);
mRecyclerView.setAdapter(mAdapter);
- updateData(mBubbleController.getOverflowBubbles());
+ onDataChanged(mBubbleController.getOverflowBubbles());
mBubbleController.setOverflowCallback(() -> {
- updateData(mBubbleController.getOverflowBubbles());
+ onDataChanged(mBubbleController.getOverflowBubbles());
});
}
@@ -87,7 +92,7 @@ public class BubbleOverflowActivity extends Activity {
findViewById(android.R.id.content).setBackgroundColor(bgColor);
}
- void updateData(List<Bubble> bubbles) {
+ void onDataChanged(List<Bubble> bubbles) {
mOverflowBubbles.clear();
if (bubbles.size() > mMaxBubbles) {
mOverflowBubbles.addAll(bubbles.subList(mMaxBubbles, bubbles.size()));
@@ -96,6 +101,12 @@ public class BubbleOverflowActivity extends Activity {
}
mAdapter.notifyDataSetChanged();
+ if (mOverflowBubbles.isEmpty()) {
+ mEmptyState.setVisibility(View.VISIBLE);
+ } else {
+ mEmptyState.setVisibility(View.GONE);
+ }
+
if (DEBUG_OVERFLOW) {
Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
mOverflowBubbles, /*selected*/ null));
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index bce172b89187..cff78cfeaae0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -528,6 +528,12 @@ public class BubbleStackView extends FrameLayout {
mBubbleContainer.addView(mOverflowBtn, 0,
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ setOverflowBtnTheme();
+ mOverflowBtn.setVisibility(GONE);
+ }
+
+ // TODO(b/149146374) Propagate theme change to bubbles in overflow.
+ private void setOverflowBtnTheme() {
TypedArray ta = mContext.obtainStyledAttributes(
new int[]{android.R.attr.colorBackgroundFloating});
int bgColor = ta.getColor(0, Color.WHITE /* default */);
@@ -537,8 +543,6 @@ public class BubbleStackView extends FrameLayout {
ColorDrawable bg = new ColorDrawable(bgColor);
AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg);
mOverflowBtn.setImageDrawable(adaptiveIcon);
-
- mOverflowBtn.setVisibility(GONE);
}
void showExpandedViewContents(int displayId) {
@@ -568,6 +572,9 @@ public class BubbleStackView extends FrameLayout {
*/
public void onThemeChanged() {
setUpFlyout();
+ if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
+ setOverflowBtnTheme();
+ }
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -795,6 +802,7 @@ public class BubbleStackView extends FrameLayout {
if (removedIndex >= 0) {
mBubbleContainer.removeViewAt(removedIndex);
bubble.cleanupExpandedState();
+ bubble.setInflated(false);
logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
} else {
Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 6d4b13ccf494..91d2de760696 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -638,9 +638,9 @@ public class NotifCollection implements Dumpable {
private static boolean shouldDismissOnClearAll(
NotificationEntry entry,
@UserIdInt int userId) {
- // TODO: (b/149396544) add FLAG_BUBBLE check here + in NoManService
return userIdMatches(entry, userId)
&& entry.isClearable()
+ && !hasFlag(entry, Notification.FLAG_BUBBLE)
&& entry.getDismissState() != DISMISSED;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 4f27c0f04c3f..5b4a927bb8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -26,26 +26,23 @@ import android.content.Context
import android.content.DialogInterface
import android.graphics.Color
import android.graphics.PixelFormat
-import android.graphics.drawable.Drawable
import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.Window
-import android.view.WindowInsets.Type
import android.view.WindowInsets.Type.statusBars
import android.view.WindowManager
import android.widget.TextView
import com.android.internal.annotations.VisibleForTesting
-
import com.android.systemui.R
-
import javax.inject.Inject
import javax.inject.Singleton
-const val TAG = "ChannelDialogController"
+private const val TAG = "ChannelDialogController"
/**
* ChannelEditorDialogController is the controller for the dialog half-shelf
@@ -149,9 +146,9 @@ class ChannelEditorDialogController @Inject constructor(
val channels = groupList
.flatMap { group ->
group.channels.asSequence().filterNot { channel ->
- channel.isImportanceLockedByOEM
- || channel.importance == IMPORTANCE_NONE
- || channel.isImportanceLockedByCriticalDeviceFunction
+ channel.isImportanceLockedByOEM ||
+ channel.importance == IMPORTANCE_NONE ||
+ channel.isImportanceLockedByCriticalDeviceFunction
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index e2513dac44d8..d744fc398d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -74,11 +74,15 @@ import javax.inject.Singleton;
@Singleton
public final class NotifBindPipeline {
private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>();
+ private final NotifBindPipelineLogger mLogger;
private BindStage mStage;
@Inject
- NotifBindPipeline(CommonNotifCollection collection) {
+ NotifBindPipeline(
+ CommonNotifCollection collection,
+ NotifBindPipelineLogger logger) {
collection.addCollectionListener(mCollectionListener);
+ mLogger = logger;
}
/**
@@ -86,6 +90,8 @@ public final class NotifBindPipeline {
*/
public void setStage(
BindStage stage) {
+ mLogger.logStageSet(stage.getClass().getName());
+
mStage = stage;
mStage.setBindRequestListener(this::onBindRequested);
}
@@ -96,6 +102,8 @@ public final class NotifBindPipeline {
public void manageRow(
@NonNull NotificationEntry entry,
@NonNull ExpandableNotificationRow row) {
+ mLogger.logManagedRow(entry.getKey());
+
final BindEntry bindEntry = getBindEntry(entry);
bindEntry.row = row;
if (bindEntry.invalidated) {
@@ -130,6 +138,8 @@ public final class NotifBindPipeline {
* callbacks when the run finishes. If a run is already in progress, it is restarted.
*/
private void startPipeline(NotificationEntry entry) {
+ mLogger.logStartPipeline(entry.getKey());
+
if (mStage == null) {
throw new IllegalStateException("No stage was ever set on the pipeline");
}
@@ -147,10 +157,11 @@ public final class NotifBindPipeline {
private void onPipelineComplete(NotificationEntry entry) {
final BindEntry bindEntry = getBindEntry(entry);
+ final Set<BindCallback> callbacks = bindEntry.callbacks;
- bindEntry.invalidated = false;
+ mLogger.logFinishedPipeline(entry.getKey(), callbacks.size());
- final Set<BindCallback> callbacks = bindEntry.callbacks;
+ bindEntry.invalidated = false;
for (BindCallback cb : callbacks) {
cb.onBindFinished(entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
new file mode 100644
index 000000000000..2717d7ad143b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+class NotifBindPipelineLogger @Inject constructor(
+ @NotificationLog private val buffer: LogBuffer
+) {
+ fun logStageSet(stageName: String) {
+ buffer.log(TAG, INFO, {
+ str1 = stageName
+ }, {
+ "Stage set: $str1"
+ })
+ }
+
+ fun logManagedRow(notifKey: String) {
+ buffer.log(TAG, INFO, {
+ str1 = notifKey
+ }, {
+ "Row set for notif: $str1"
+ })
+ }
+
+ fun logStartPipeline(notifKey: String) {
+ buffer.log(TAG, INFO, {
+ str1 = notifKey
+ }, {
+ "Start pipeline for notif: $str1"
+ })
+ }
+
+ fun logFinishedPipeline(notifKey: String, numCallbacks: Int) {
+ buffer.log(TAG, INFO, {
+ str1 = notifKey
+ int1 = numCallbacks
+ }, {
+ "Finished pipeline for notif $str1 with $int1 callbacks"
+ })
+ }
+}
+
+private const val TAG = "NotifBindPipeline" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index 5170d0b85b17..88ed0bb37d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -157,6 +157,15 @@ public final class RowContentBindParams {
return mViewsNeedReinflation;
}
+ @Override
+ public String toString() {
+ return String.format("RowContentBindParams[mContentViews=%x mDirtyContentViews=%x "
+ + "mUseLowPriority=%b mUseChildInGroup=%b mUseIncreasedHeight=%b "
+ + "mUseIncreasedHeadsUpHeight=%b mViewsNeedReinflation=%b]",
+ mContentViews, mDirtyContentViews, mUseLowPriority, mUseChildInGroup,
+ mUseIncreasedHeight, mUseIncreasedHeadsUpHeight, mViewsNeedReinflation);
+ }
+
/**
* Content views that should be inflated by default for all notifications.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index f78324596fb4..c632f3eb22a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -38,13 +38,16 @@ import javax.inject.Singleton;
public class RowContentBindStage extends BindStage<RowContentBindParams> {
private final NotificationRowContentBinder mBinder;
private final NotifInflationErrorManager mNotifInflationErrorManager;
+ private final RowContentBindStageLogger mLogger;
@Inject
RowContentBindStage(
NotificationRowContentBinder binder,
- NotifInflationErrorManager errorManager) {
+ NotifInflationErrorManager errorManager,
+ RowContentBindStageLogger logger) {
mBinder = binder;
mNotifInflationErrorManager = errorManager;
+ mLogger = logger;
}
@Override
@@ -54,6 +57,8 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
@NonNull StageCallback callback) {
RowContentBindParams params = getStageParams(entry);
+ mLogger.logStageParams(entry.getKey(), params.toString());
+
// Resolve content to bind/unbind.
@InflationFlag int inflationFlags = params.getContentViews();
@InflationFlag int invalidatedFlags = params.getDirtyContentViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
new file mode 100644
index 000000000000..29cce3375c8a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+class RowContentBindStageLogger @Inject constructor(
+ @NotificationLog private val buffer: LogBuffer
+) {
+ fun logStageParams(notifKey: String, stageParams: String) {
+ buffer.log(TAG, INFO, {
+ str1 = notifKey
+ str2 = stageParams
+ }, {
+ "Invalidated notif $str1 with params: \n$str2"
+ })
+ }
+}
+
+private const val TAG = "RowContentBindStage" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 70b43bfc0367..e1a20b6ac5d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -365,8 +365,12 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
} catch (RemoteException ex) {
// system process is dead if we're here.
}
+
if (!isBubble) {
if (parentToCancelFinal != null) {
+ // TODO: (b/145659174) remove - this cancels the parent if the notification clicked
+ // on will auto-cancel and is the only child in the group. This won't be
+ // necessary in the new pipeline due to group pruning in ShadeListBuilder.
removeNotification(parentToCancelFinal);
}
if (shouldAutoCancel(sbn)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
index 408bba48d422..6408f7a38133 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
@@ -59,7 +59,7 @@ public class NotifBindPipelineTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
CommonNotifCollection collection = mock(CommonNotifCollection.class);
- mBindPipeline = new NotifBindPipeline(collection);
+ mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class));
mBindPipeline.setStage(mStage);
ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index fd5512d62968..7a1bd052a336 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -111,11 +111,13 @@ public class NotificationTestHelper {
mock(NotifRemoteViewCache.class),
mock(NotificationRemoteInputManager.class));
contentBinder.setInflateSynchronously(true);
- mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class));
+ mBindStage = new RowContentBindStage(contentBinder,
+ mock(NotifInflationErrorManager.class),
+ mock(RowContentBindStageLogger.class));
CommonNotifCollection collection = mock(CommonNotifCollection.class);
- mBindPipeline = new NotifBindPipeline(collection);
+ mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class));
mBindPipeline.setStage(mBindStage);
ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index d9fe6551ba1c..0f2482ce9c4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -60,8 +60,10 @@ public class RowContentBindStageTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
- mRowContentBindStage = new RowContentBindStage(mBinder,
- mock(NotifInflationErrorManager.class));
+ mRowContentBindStage = new RowContentBindStage(
+ mBinder,
+ mock(NotifInflationErrorManager.class),
+ mock(RowContentBindStageLogger.class));
mRowContentBindStage.createStageParams(mEntry);
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 31ea5faa05f1..9a33fc9548a0 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -998,6 +998,11 @@ public final class ContentCaptureManagerService extends
sendErrorSignal(mClientAdapterReference, serviceAdapterReference,
ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+ } finally {
+ synchronized (parentService.mLock) {
+ parentService.mPackagesWithShareRequests
+ .remove(mDataShareRequest.getPackageName());
+ }
}
});
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index c98762074b53..9540f4336464 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -1556,16 +1556,16 @@ public class IpSecService extends IIpSecService.Stub {
}
Objects.requireNonNull(callingPackage, "Null calling package cannot create IpSec tunnels");
- switch (getAppOpsManager().noteOp(TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
- case AppOpsManager.MODE_DEFAULT:
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
- break;
- case AppOpsManager.MODE_ALLOWED:
- return;
- default:
- throw new SecurityException("Request to ignore AppOps for non-legacy API");
+
+ // OP_MANAGE_IPSEC_TUNNELS will return MODE_ERRORED by default, including for the system
+ // server. If the appop is not granted, require that the caller has the MANAGE_IPSEC_TUNNELS
+ // permission or is the System Server.
+ if (AppOpsManager.MODE_ALLOWED == getAppOpsManager().noteOpNoThrow(
+ TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
+ return;
}
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
}
private void createOrUpdateTransform(
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 97a8b8764bcf..67d7530fa11e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -128,6 +128,7 @@ import android.util.Log;
import android.util.MathUtils;
import android.util.PrintWriterPrinter;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
@@ -247,6 +248,7 @@ public class AudioService extends IAudioService.Stub
// AudioHandler messages
private static final int MSG_SET_DEVICE_VOLUME = 0;
private static final int MSG_PERSIST_VOLUME = 1;
+ private static final int MSG_PERSIST_VOLUME_GROUP = 2;
private static final int MSG_PERSIST_RINGER_MODE = 3;
private static final int MSG_AUDIO_SERVER_DIED = 4;
private static final int MSG_PLAY_SOUND_EFFECT = 5;
@@ -780,6 +782,10 @@ public class AudioService extends IAudioService.Stub
mSettingsObserver = new SettingsObserver();
createStreamStates();
+ // must be called after createStreamStates() as it uses MUSIC volume as default if no
+ // persistent data
+ initVolumeGroupStates();
+
// mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
// relies on audio policy having correct ranges for volume indexes.
mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
@@ -1018,6 +1024,9 @@ public class AudioService extends IAudioService.Stub
streamState.applyAllVolumes();
}
+ // Restore audio volume groups
+ restoreVolumeGroups();
+
// Restore mono mode
updateMasterMono(mContentResolver);
@@ -2288,20 +2297,20 @@ public class AudioService extends IAudioService.Stub
String callingPackage) {
enforceModifyAudioRoutingPermission();
Objects.requireNonNull(attr, "attr must not be null");
- // @todo not hold the caller context, post message
- int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
- final int device = getDeviceForStream(stream);
-
- int oldIndex = AudioSystem.getVolumeIndexForAttributes(attr, device);
-
- AudioSystem.setVolumeIndexForAttributes(attr, index, device);
-
final int volumeGroup = getVolumeGroupIdForAttributes(attr);
- final AudioVolumeGroup avg = getAudioVolumeGroupById(volumeGroup);
- if (avg == null) {
+ if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+ Log.e(TAG, ": no volume group found for attributes " + attr.toString());
return;
}
- for (final int groupedStream : avg.getLegacyStreamTypes()) {
+ final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
+ index/*val1*/, flags/*val2*/, callingPackage));
+
+ vgs.setVolumeIndex(index, flags);
+
+ // For legacy reason, propagate to all streams associated to this volume group
+ for (final int groupedStream : vgs.getLegacyStreamTypes()) {
setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
Binder.getCallingUid());
}
@@ -2323,10 +2332,12 @@ public class AudioService extends IAudioService.Stub
public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
enforceModifyAudioRoutingPermission();
Objects.requireNonNull(attr, "attr must not be null");
- int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
- final int device = getDeviceForStream(stream);
-
- return AudioSystem.getVolumeIndexForAttributes(attr, device);
+ final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+ if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+ throw new IllegalArgumentException("No volume group for attributes " + attr);
+ }
+ final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+ return vgs.getVolumeIndex();
}
/** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
@@ -3754,6 +3765,8 @@ public class AudioService extends IAudioService.Stub
enforceSafeMediaVolume(TAG);
}
}
+
+ readVolumeGroupsSettings();
}
/** @see AudioManager#setSpeakerphoneOn(boolean) */
@@ -4654,6 +4667,310 @@ public class AudioService extends IAudioService.Stub
///////////////////////////////////////////////////////////////////////////
// Inner classes
///////////////////////////////////////////////////////////////////////////
+ /**
+ * Key is the AudioManager VolumeGroupId
+ * Value is the VolumeGroupState
+ */
+ private static final SparseArray<VolumeGroupState> sVolumeGroupStates = new SparseArray<>();
+
+ private void initVolumeGroupStates() {
+ for (final AudioVolumeGroup avg : getAudioVolumeGroups()) {
+ try {
+ // if no valid attributes, this volume group is not controllable, throw exception
+ ensureValidAttributes(avg);
+ } catch (IllegalArgumentException e) {
+ // Volume Groups without attributes are not controllable through set/get volume
+ // using attributes. Do not append them.
+ Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+ continue;
+ }
+ sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
+ }
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.applyAllVolumes();
+ }
+ }
+
+ private void ensureValidAttributes(AudioVolumeGroup avg) {
+ boolean hasAtLeastOneValidAudioAttributes = avg.getAudioAttributes().stream()
+ .anyMatch(aa -> !aa.equals(AudioProductStrategy.sDefaultAttributes));
+ if (!hasAtLeastOneValidAudioAttributes) {
+ throw new IllegalArgumentException("Volume Group " + avg.name()
+ + " has no valid audio attributes");
+ }
+ }
+
+ private void readVolumeGroupsSettings() {
+ Log.v(TAG, "readVolumeGroupsSettings");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.readSettings();
+ vgs.applyAllVolumes();
+ }
+ }
+
+ // Called upon crash of AudioServer
+ private void restoreVolumeGroups() {
+ Log.v(TAG, "restoreVolumeGroups");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.applyAllVolumes();
+ }
+ }
+
+ private void dumpVolumeGroups(PrintWriter pw) {
+ pw.println("\nVolume Groups (device: index)");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.dump(pw);
+ pw.println("");
+ }
+ }
+
+ // NOTE: Locking order for synchronized objects related to volume management:
+ // 1 mSettingsLock
+ // 2 VolumeGroupState.class
+ private class VolumeGroupState {
+ private final AudioVolumeGroup mAudioVolumeGroup;
+ private final SparseIntArray mIndexMap = new SparseIntArray(8);
+ private int mIndexMin;
+ private int mIndexMax;
+ private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
+ private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
+ private AudioAttributes mAudioAttributes = AudioProductStrategy.sDefaultAttributes;
+
+ // No API in AudioSystem to get a device from strategy or from attributes.
+ // Need a valid public stream type to use current API getDeviceForStream
+ private int getDeviceForVolume() {
+ return getDeviceForStream(mPublicStreamType);
+ }
+
+ private VolumeGroupState(AudioVolumeGroup avg) {
+ mAudioVolumeGroup = avg;
+ Log.v(TAG, "VolumeGroupState for " + avg.toString());
+ for (final AudioAttributes aa : avg.getAudioAttributes()) {
+ if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
+ mAudioAttributes = aa;
+ break;
+ }
+ }
+ final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+ if (streamTypes.length != 0) {
+ // Uses already initialized MIN / MAX if a stream type is attached to group
+ mLegacyStreamType = streamTypes[0];
+ for (final int streamType : streamTypes) {
+ if (streamType != AudioSystem.STREAM_DEFAULT
+ && streamType < AudioSystem.getNumStreamTypes()) {
+ mPublicStreamType = streamType;
+ break;
+ }
+ }
+ mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
+ mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
+ } else if (!avg.getAudioAttributes().isEmpty()) {
+ mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
+ mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
+ } else {
+ Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
+ + " has neither valid attributes nor valid stream types assigned");
+ return;
+ }
+ // Load volume indexes from data base
+ readSettings();
+ }
+
+ public @NonNull int[] getLegacyStreamTypes() {
+ return mAudioVolumeGroup.getLegacyStreamTypes();
+ }
+
+ public String name() {
+ return mAudioVolumeGroup.name();
+ }
+
+ public int getVolumeIndex() {
+ return getIndex(getDeviceForVolume());
+ }
+
+ public void setVolumeIndex(int index, int flags) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ setVolumeIndex(index, getDeviceForVolume(), flags);
+ }
+
+ private void setVolumeIndex(int index, int device, int flags) {
+ // Set the volume index
+ setVolumeIndexInt(index, device, flags);
+
+ // Update local cache
+ mIndexMap.put(device, index);
+
+ // update data base - post a persist volume group msg
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_VOLUME_GROUP,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ this,
+ PERSIST_DELAY);
+ }
+
+ private void setVolumeIndexInt(int index, int device, int flags) {
+ // Set the volume index
+ AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
+ }
+
+ public int getIndex(int device) {
+ synchronized (VolumeGroupState.class) {
+ int index = mIndexMap.get(device, -1);
+ // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+ return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
+ }
+ }
+
+ public boolean hasIndexForDevice(int device) {
+ synchronized (VolumeGroupState.class) {
+ return (mIndexMap.get(device, -1) != -1);
+ }
+ }
+
+ public int getMaxIndex() {
+ return mIndexMax;
+ }
+
+ public int getMinIndex() {
+ return mIndexMin;
+ }
+
+ public void applyAllVolumes() {
+ synchronized (VolumeGroupState.class) {
+ if (mLegacyStreamType != AudioSystem.STREAM_DEFAULT) {
+ // No-op to avoid regression with stream based volume management
+ return;
+ }
+ // apply device specific volumes first
+ int index;
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ final int device = mIndexMap.keyAt(i);
+ if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
+ index = mIndexMap.valueAt(i);
+ Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ setVolumeIndexInt(index, device, 0 /*flags*/);
+ }
+ }
+ // apply default volume last: by convention , default device volume will be used
+ index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+ Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
+ + mAudioVolumeGroup.name());
+ setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+ }
+ }
+
+ private void persistVolumeGroup(int device) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ boolean success = Settings.System.putIntForUser(mContentResolver,
+ getSettingNameForDevice(device),
+ getIndex(device),
+ UserHandle.USER_CURRENT);
+ if (!success) {
+ Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name());
+ }
+ }
+
+ public void readSettings() {
+ synchronized (VolumeGroupState.class) {
+ // First clear previously loaded (previous user?) settings
+ mIndexMap.clear();
+ // force maximum volume on all streams if fixed volume property is set
+ if (mUseFixedVolume) {
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+ return;
+ }
+ for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+ // retrieve current volume for device
+ // if no volume stored for current volume group and device, use default volume
+ // if default device, continue otherwise
+ int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT)
+ ? AudioSystem.DEFAULT_STREAM_VOLUME[mPublicStreamType] : -1;
+ int index;
+ String name = getSettingNameForDevice(device);
+ index = Settings.System.getIntForUser(
+ mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+ if (index == -1) {
+ Log.e(TAG, "readSettings: No index stored for group "
+ + mAudioVolumeGroup.name() + ", device " + name);
+ continue;
+ }
+ Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+ + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+ mIndexMap.put(device, getValidIndex(index));
+ }
+ }
+ }
+
+ private int getValidIndex(int index) {
+ if (index < mIndexMin) {
+ return mIndexMin;
+ } else if (mUseFixedVolume || index > mIndexMax) {
+ return mIndexMax;
+ }
+ return index;
+ }
+
+ public @NonNull String getSettingNameForDevice(int device) {
+ final String suffix = AudioSystem.getOutputDeviceName(device);
+ if (suffix.isEmpty()) {
+ return mAudioVolumeGroup.name();
+ }
+ return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
+ pw.print(" Min: ");
+ pw.println(mIndexMin);
+ pw.print(" Max: ");
+ pw.println(mIndexMax);
+ pw.print(" Current: ");
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ final int device = mIndexMap.keyAt(i);
+ pw.print(Integer.toHexString(device));
+ final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+ : AudioSystem.getOutputDeviceName(device);
+ if (!deviceName.isEmpty()) {
+ pw.print(" (");
+ pw.print(deviceName);
+ pw.print(")");
+ }
+ pw.print(": ");
+ pw.print(mIndexMap.valueAt(i));
+ }
+ pw.println();
+ pw.print(" Devices: ");
+ int n = 0;
+ final int devices = getDeviceForVolume();
+ for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+ if ((devices & device) == device) {
+ if (n++ > 0) {
+ pw.print(", ");
+ }
+ pw.print(AudioSystem.getOutputDeviceName(device));
+ }
+ }
+ }
+ }
+
// NOTE: Locking order for synchronized objects related to volume or ringer mode management:
// 1 mScoclient OR mSafeMediaVolumeState
@@ -5298,6 +5615,11 @@ public class AudioService extends IAudioService.Stub
persistVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
+ case MSG_PERSIST_VOLUME_GROUP:
+ final VolumeGroupState vgs = (VolumeGroupState) msg.obj;
+ vgs.persistVolumeGroup(msg.arg1);
+ break;
+
case MSG_PERSIST_RINGER_MODE:
// note that the value persisted is the current ringer mode, not the
// value of ringer mode as of the time the request was made to persist
@@ -6395,6 +6717,7 @@ public class AudioService extends IAudioService.Stub
}
mMediaFocusControl.dump(pw);
dumpStreamStates(pw);
+ dumpVolumeGroups(pw);
dumpRingerMode(pw);
pw.println("\nAudio routes:");
pw.print(" mMainType=0x"); pw.println(Integer.toHexString(
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index fcd8701f4ccf..add620e74df0 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -16,6 +16,7 @@
package com.android.server.audio;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -97,12 +98,15 @@ public class AudioServiceEvents {
static final int VOL_ADJUST_VOL_UID = 5;
static final int VOL_VOICE_ACTIVITY_HEARING_AID = 6;
static final int VOL_MODE_CHANGE_HEARING_AID = 7;
+ static final int VOL_SET_GROUP_VOL = 8;
final int mOp;
final int mStream;
final int mVal1;
final int mVal2;
final String mCaller;
+ final String mGroupName;
+ final AudioAttributes mAudioAttributes;
/** used for VOL_ADJUST_VOL_UID,
* VOL_ADJUST_SUGG_VOL,
@@ -114,6 +118,8 @@ public class AudioServiceEvents {
mVal1 = val1;
mVal2 = val2;
mCaller = caller;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_SET_HEARING_AID_VOL*/
@@ -124,6 +130,8 @@ public class AudioServiceEvents {
// unused
mStream = -1;
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_SET_AVRCP_VOL */
@@ -134,6 +142,8 @@ public class AudioServiceEvents {
mVal2 = 0;
mStream = -1;
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_VOICE_ACTIVITY_HEARING_AID */
@@ -144,6 +154,8 @@ public class AudioServiceEvents {
mVal2 = voiceActive ? 1 : 0;
// unused
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_MODE_CHANGE_HEARING_AID */
@@ -154,6 +166,19 @@ public class AudioServiceEvents {
mVal2 = mode;
// unused
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
+ }
+
+ /** used for VOL_SET_GROUP_VOL */
+ VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) {
+ mOp = op;
+ mStream = -1;
+ mVal1 = index;
+ mVal2 = flags;
+ mCaller = caller;
+ mGroupName = group;
+ mAudioAttributes = aa;
}
@Override
@@ -208,6 +233,14 @@ public class AudioServiceEvents {
.append(") causes setting HEARING_AID volume to idx:").append(mVal1)
.append(" stream:").append(AudioSystem.streamToString(mStream))
.toString();
+ case VOL_SET_GROUP_VOL:
+ return new StringBuilder("setVolumeIndexForAttributes(attr:")
+ .append(mAudioAttributes.toString())
+ .append(" group: ").append(mGroupName)
+ .append(" index:").append(mVal1)
+ .append(" flags:0x").append(Integer.toHexString(mVal2))
+ .append(") from ").append(mCaller)
+ .toString();
default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
}
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index c9c2c96a642a..204f072a72fc 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -153,7 +153,12 @@ public class AuthService extends SystemService {
// Only allow internal clients to enable non-public options.
if (bundle.getBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS)
- || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) {
+ || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)
+ || bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE) != null
+ || bundle.getCharSequence(
+ BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE) != null
+ || bundle.getCharSequence(
+ BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION) != null) {
checkInternalPermission();
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index cb88c4e8a739..1a68f1bff605 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -48,8 +48,12 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
import android.net.INetworkManagementEventObserver;
+import android.net.Ikev2VpnProfile;
import android.net.IpPrefix;
import android.net.IpSecManager;
+import android.net.IpSecManager.IpSecTunnelInterface;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalSocket;
@@ -65,6 +69,12 @@ import android.net.RouteInfo;
import android.net.UidRange;
import android.net.VpnManager;
import android.net.VpnService;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionParams;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -113,6 +123,7 @@ import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -122,6 +133,9 @@ import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -176,14 +190,14 @@ public class Vpn {
private final Context mContext;
private final NetworkInfo mNetworkInfo;
- private String mPackage;
+ @VisibleForTesting protected String mPackage;
private int mOwnerUID;
private boolean mIsPackageTargetingAtLeastQ;
private String mInterface;
private Connection mConnection;
/** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
- private VpnRunner mVpnRunner;
+ @VisibleForTesting protected VpnRunner mVpnRunner;
private PendingIntent mStatusIntent;
private volatile boolean mEnableTeardown = true;
@@ -196,6 +210,7 @@ public class Vpn {
@VisibleForTesting
protected final NetworkCapabilities mNetworkCapabilities;
private final SystemServices mSystemServices;
+ private final Ikev2SessionCreator mIkev2SessionCreator;
/**
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
@@ -238,17 +253,20 @@ public class Vpn {
public Vpn(Looper looper, Context context, INetworkManagementService netService,
@UserIdInt int userHandle) {
- this(looper, context, netService, userHandle, new SystemServices(context));
+ this(looper, context, netService, userHandle,
+ new SystemServices(context), new Ikev2SessionCreator());
}
@VisibleForTesting
protected Vpn(Looper looper, Context context, INetworkManagementService netService,
- int userHandle, SystemServices systemServices) {
+ int userHandle, SystemServices systemServices,
+ Ikev2SessionCreator ikev2SessionCreator) {
mContext = context;
mNetd = netService;
mUserHandle = userHandle;
mLooper = looper;
mSystemServices = systemServices;
+ mIkev2SessionCreator = ikev2SessionCreator;
mPackage = VpnConfig.LEGACY_VPN;
mOwnerUID = getAppUid(mPackage, mUserHandle);
@@ -749,8 +767,9 @@ public class Vpn {
private boolean isCurrentPreparedPackage(String packageName) {
// We can't just check that packageName matches mPackage, because if the app was uninstalled
- // and reinstalled it will no longer be prepared. Instead check the UID.
- return getAppUid(packageName, mUserHandle) == mOwnerUID;
+ // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
+ // calling package may not be the same as the prepared package. Check both UID and package.
+ return getAppUid(packageName, mUserHandle) == mOwnerUID && mPackage.equals(packageName);
}
/** Prepare the VPN for the given package. Does not perform permission checks. */
@@ -979,7 +998,11 @@ public class Vpn {
}
lp.setDomains(buffer.toString().trim());
- // TODO: Stop setting the MTU in jniCreate and set it here.
+ if (mConfig.mtu > 0) {
+ lp.setMtu(mConfig.mtu);
+ }
+
+ // TODO: Stop setting the MTU in jniCreate
return lp;
}
@@ -2004,30 +2027,369 @@ public class Vpn {
protected abstract void exit();
}
- private class IkeV2VpnRunner extends VpnRunner {
- private static final String TAG = "IkeV2VpnRunner";
+ interface IkeV2VpnRunnerCallback {
+ void onDefaultNetworkChanged(@NonNull Network network);
- private final IpSecManager mIpSecManager;
- private final VpnProfile mProfile;
+ void onChildOpened(
+ @NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
+
+ void onChildTransformCreated(
+ @NonNull Network network, @NonNull IpSecTransform transform, int direction);
+
+ void onSessionLost(@NonNull Network network);
+ }
+
+ /**
+ * Internal class managing IKEv2/IPsec VPN connectivity
+ *
+ * <p>The IKEv2 VPN will listen to, and run based on the lifecycle of Android's default Network.
+ * As a new default is selected, old IKE sessions will be torn down, and a new one will be
+ * started.
+ *
+ * <p>This class uses locking minimally - the Vpn instance lock is only ever held when fields of
+ * the outer class are modified. As such, care must be taken to ensure that no calls are added
+ * that might modify the outer class' state without acquiring a lock.
+ *
+ * <p>The overall structure of the Ikev2VpnRunner is as follows:
+ *
+ * <ol>
+ * <li>Upon startup, a NetworkRequest is registered with ConnectivityManager. This is called
+ * any time a new default network is selected
+ * <li>When a new default is connected, an IKE session is started on that Network. If there
+ * were any existing IKE sessions on other Networks, they are torn down before starting
+ * the new IKE session
+ * <li>Upon establishment, the onChildTransformCreated() callback is called twice, one for
+ * each direction, and finally onChildOpened() is called
+ * <li>Upon the onChildOpened() call, the VPN is fully set up.
+ * <li>Subsequent Network changes result in new onDefaultNetworkChanged() callbacks. See (2).
+ * </ol>
+ */
+ class IkeV2VpnRunner extends VpnRunner implements IkeV2VpnRunnerCallback {
+ @NonNull private static final String TAG = "IkeV2VpnRunner";
+
+ @NonNull private final IpSecManager mIpSecManager;
+ @NonNull private final Ikev2VpnProfile mProfile;
+ @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
+
+ /**
+ * Executor upon which ALL callbacks must be run.
+ *
+ * <p>This executor MUST be a single threaded executor, in order to ensure the consistency
+ * of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
+ * virtue of everything being serialized on this executor.
+ */
+ @NonNull private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
- IkeV2VpnRunner(VpnProfile profile) {
+ /** Signal to ensure shutdown is honored even if a new Network is connected. */
+ private boolean mIsRunning = true;
+
+ @Nullable private UdpEncapsulationSocket mEncapSocket;
+ @Nullable private IpSecTunnelInterface mTunnelIface;
+ @Nullable private IkeSession mSession;
+ @Nullable private Network mActiveNetwork;
+
+ IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
super(TAG);
mProfile = profile;
-
- // TODO: move this to startVpnRunnerPrivileged()
- mConfig = new VpnConfig();
- mIpSecManager = mContext.getSystemService(IpSecManager.class);
+ mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this);
}
@Override
public void run() {
- // TODO: Build IKE config, start IKE session
+ // Explicitly use only the network that ConnectivityService thinks is the "best." In
+ // other words, only ever use the currently selected default network. This does mean
+ // that in both onLost() and onConnected(), any old sessions MUST be torn down. This
+ // does NOT include VPNs.
+ final ConnectivityManager cm = ConnectivityManager.from(mContext);
+ cm.requestNetwork(cm.getDefaultRequest(), mNetworkCallback);
+ }
+
+ private boolean isActiveNetwork(@Nullable Network network) {
+ return Objects.equals(mActiveNetwork, network) && mIsRunning;
+ }
+
+ /**
+ * Called when an IKE Child session has been opened, signalling completion of the startup.
+ *
+ * <p>This method is only ever called once per IkeSession, and MUST run on the mExecutor
+ * thread in order to ensure consistency of the Ikev2VpnRunner fields.
+ */
+ public void onChildOpened(
+ @NonNull Network network, @NonNull ChildSessionConfiguration childConfig) {
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "onOpened called for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+ // IKE session was already shut down (exited, or an error was encountered somewhere
+ // else). In both cases, all resources and sessions are torn down via
+ // resetIkeState().
+ return;
+ }
+
+ try {
+ final String interfaceName = mTunnelIface.getInterfaceName();
+ final int maxMtu = mProfile.getMaxMtu();
+ final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses();
+
+ final Collection<RouteInfo> newRoutes = VpnIkev2Utils.getRoutesFromTrafficSelectors(
+ childConfig.getOutboundTrafficSelectors());
+ for (final LinkAddress address : internalAddresses) {
+ mTunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
+ }
+
+ final NetworkAgent networkAgent;
+ final LinkProperties lp;
+
+ synchronized (Vpn.this) {
+ mInterface = interfaceName;
+ mConfig.mtu = maxMtu;
+ mConfig.interfaze = mInterface;
+
+ mConfig.addresses.clear();
+ mConfig.addresses.addAll(internalAddresses);
+
+ mConfig.routes.clear();
+ mConfig.routes.addAll(newRoutes);
+
+ // TODO: Add DNS servers from negotiation
+
+ networkAgent = mNetworkAgent;
+
+ // The below must be done atomically with the mConfig update, otherwise
+ // isRunningLocked() will be racy.
+ if (networkAgent == null) {
+ if (isSettingsVpnLocked()) {
+ prepareStatusIntent();
+ }
+ agentConnect();
+ return; // Link properties are already sent.
+ }
+
+ lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
+ }
+
+ networkAgent.sendLinkProperties(lp);
+ } catch (Exception e) {
+ Log.d(TAG, "Error in ChildOpened for network " + network, e);
+ onSessionLost(network);
+ }
+ }
+
+ /**
+ * Called when an IPsec transform has been created, and should be applied.
+ *
+ * <p>This method is called multiple times over the lifetime of an IkeSession (or default
+ * network), and is MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ public void onChildTransformCreated(
+ @NonNull Network network, @NonNull IpSecTransform transform, int direction) {
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "ChildTransformCreated for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+ // IKE session was already shut down (exited, or an error was encountered somewhere
+ // else). In both cases, all resources and sessions are torn down via
+ // resetIkeState().
+ return;
+ }
+
+ try {
+ // Transforms do not need to be persisted; the IkeSession will keep
+ // them alive for us
+ mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
+ } catch (IOException e) {
+ Log.d(TAG, "Transform application failed for network " + network, e);
+ onSessionLost(network);
+ }
+ }
+
+ /**
+ * Called when a new default network is connected.
+ *
+ * <p>The Ikev2VpnRunner will unconditionally switch to the new network, killing the old IKE
+ * state in the process, and starting a new IkeSession instance.
+ *
+ * <p>This method is called multiple times over the lifetime of the Ikev2VpnRunner, and is
+ * called on the ConnectivityService thread. Thus, the actual work MUST be proxied to the
+ * mExecutor thread in order to ensure consistency of the Ikev2VpnRunner fields.
+ */
+ public void onDefaultNetworkChanged(@NonNull Network network) {
+ Log.d(TAG, "Starting IKEv2/IPsec session on new network: " + network);
+
+ // Proxy to the Ikev2VpnRunner (single-thread) executor to ensure consistency in lieu
+ // of locking.
+ mExecutor.execute(() -> {
+ try {
+ if (!mIsRunning) {
+ Log.d(TAG, "onDefaultNetworkChanged after exit");
+ return; // VPN has been shut down.
+ }
+
+ // Without MOBIKE, we have no way to seamlessly migrate. Close on old
+ // (non-default) network, and start the new one.
+ resetIkeState();
+ mActiveNetwork = network;
+
+ // TODO(b/149356682): Update this based on new IKE API
+ mEncapSocket = mIpSecManager.openUdpEncapsulationSocket();
+
+ // TODO(b/149356682): Update this based on new IKE API
+ final IkeSessionParams ikeSessionParams =
+ VpnIkev2Utils.buildIkeSessionParams(mProfile, mEncapSocket);
+ final ChildSessionParams childSessionParams =
+ VpnIkev2Utils.buildChildSessionParams();
+
+ // TODO: Remove the need for adding two unused addresses with
+ // IPsec tunnels.
+ mTunnelIface =
+ mIpSecManager.createIpSecTunnelInterface(
+ ikeSessionParams.getServerAddress() /* unused */,
+ ikeSessionParams.getServerAddress() /* unused */,
+ network);
+ mNetd.setInterfaceUp(mTunnelIface.getInterfaceName());
+
+ // Socket must be bound to prevent network switches from causing
+ // the IKE teardown to fail/timeout.
+ // TODO(b/149356682): Update this based on new IKE API
+ network.bindSocket(mEncapSocket.getFileDescriptor());
+
+ mSession = mIkev2SessionCreator.createIkeSession(
+ mContext,
+ ikeSessionParams,
+ childSessionParams,
+ mExecutor,
+ new VpnIkev2Utils.IkeSessionCallbackImpl(
+ TAG, IkeV2VpnRunner.this, network),
+ new VpnIkev2Utils.ChildSessionCallbackImpl(
+ TAG, IkeV2VpnRunner.this, network));
+ Log.d(TAG, "Ike Session started for network " + network);
+ } catch (Exception e) {
+ Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
+ onSessionLost(network);
+ }
+ });
+ }
+
+ /**
+ * Handles loss of a session
+ *
+ * <p>The loss of a session might be due to an onLost() call, the IKE session getting torn
+ * down for any reason, or an error in updating state (transform application, VPN setup)
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ public void onSessionLost(@NonNull Network network) {
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "onSessionLost() called for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+ // IKE session was already shut down (exited, or an error was encountered somewhere
+ // else). In both cases, all resources and sessions are torn down via
+ // onSessionLost() and resetIkeState().
+ return;
+ }
+
+ mActiveNetwork = null;
+
+ // Close all obsolete state, but keep VPN alive incase a usable network comes up.
+ // (Mirrors VpnService behavior)
+ Log.d(TAG, "Resetting state for network: " + network);
+
+ synchronized (Vpn.this) {
+ // Since this method handles non-fatal errors only, set mInterface to null to
+ // prevent the NetworkManagementEventObserver from killing this VPN based on the
+ // interface going down (which we expect).
+ mInterface = null;
+ mConfig.interfaze = null;
+
+ // Set as unroutable to prevent traffic leaking while the interface is down.
+ if (mConfig != null && mConfig.routes != null) {
+ final List<RouteInfo> oldRoutes = new ArrayList<>(mConfig.routes);
+
+ mConfig.routes.clear();
+ for (final RouteInfo route : oldRoutes) {
+ mConfig.routes.add(new RouteInfo(route.getDestination(), RTN_UNREACHABLE));
+ }
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendLinkProperties(makeLinkProperties());
+ }
+ }
+ }
+
+ resetIkeState();
+ }
+
+ /**
+ * Cleans up all IKE state
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ private void resetIkeState() {
+ if (mTunnelIface != null) {
+ // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down.
+ mTunnelIface.close();
+ mTunnelIface = null;
+ }
+ if (mSession != null) {
+ mSession.kill(); // Kill here to make sure all resources are released immediately
+ mSession = null;
+ }
+
+ // TODO(b/149356682): Update this based on new IKE API
+ if (mEncapSocket != null) {
+ try {
+ mEncapSocket.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close encap socket", e);
+ }
+ mEncapSocket = null;
+ }
+ }
+
+ /**
+ * Triggers cleanup of outer class' state
+ *
+ * <p>Can be called from any thread, as it does not mutate state in the Ikev2VpnRunner.
+ */
+ private void cleanupVpnState() {
+ synchronized (Vpn.this) {
+ agentDisconnect();
+ }
+ }
+
+ /**
+ * Cleans up all Ikev2VpnRunner internal state
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ private void shutdownVpnRunner() {
+ mActiveNetwork = null;
+ mIsRunning = false;
+
+ resetIkeState();
+
+ final ConnectivityManager cm = ConnectivityManager.from(mContext);
+ cm.unregisterNetworkCallback(mNetworkCallback);
+
+ mExecutor.shutdown();
}
@Override
public void exit() {
- // TODO: Teardown IKE session & any resources.
- agentDisconnect();
+ // Cleanup outer class' state immediately, otherwise race conditions may ensue.
+ cleanupVpnState();
+
+ mExecutor.execute(() -> {
+ shutdownVpnRunner();
+ });
}
}
@@ -2488,12 +2850,46 @@ public class Vpn {
throw new IllegalArgumentException("No profile found for " + packageName);
}
- startVpnProfilePrivileged(profile);
+ startVpnProfilePrivileged(profile, packageName);
});
}
- private void startVpnProfilePrivileged(@NonNull VpnProfile profile) {
- // TODO: Start PlatformVpnRunner
+ private void startVpnProfilePrivileged(
+ @NonNull VpnProfile profile, @NonNull String packageName) {
+ // Ensure that no other previous instance is running.
+ if (mVpnRunner != null) {
+ mVpnRunner.exit();
+ mVpnRunner = null;
+ }
+ updateState(DetailedState.CONNECTING, "startPlatformVpn");
+
+ try {
+ // Build basic config
+ mConfig = new VpnConfig();
+ mConfig.user = packageName;
+ mConfig.isMetered = profile.isMetered;
+ mConfig.startTime = SystemClock.elapsedRealtime();
+ mConfig.proxyInfo = profile.proxy;
+
+ switch (profile.type) {
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+ mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+ mVpnRunner.start();
+ break;
+ default:
+ updateState(DetailedState.FAILED, "Invalid platform VPN type");
+ Log.d(TAG, "Unknown VPN profile type: " + profile.type);
+ break;
+ }
+ } catch (IOException | GeneralSecurityException e) {
+ // Reset mConfig
+ mConfig = null;
+
+ updateState(DetailedState.FAILED, "VPN startup failed");
+ throw new IllegalArgumentException("VPN startup failed", e);
+ }
}
/**
@@ -2507,13 +2903,37 @@ public class Vpn {
public synchronized void stopVpnProfile(@NonNull String packageName) {
checkNotNull(packageName, "No package name provided");
- // To stop the VPN profile, the caller must be the current prepared package. Otherwise,
- // the app is not prepared, and we can just return.
- if (!isCurrentPreparedPackage(packageName)) {
- // TODO: Also check to make sure that the running VPN is a VPN profile.
+ // To stop the VPN profile, the caller must be the current prepared package and must be
+ // running an Ikev2VpnProfile.
+ if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
return;
}
prepareInternal(VpnConfig.LEGACY_VPN);
}
+
+ /**
+ * Proxy to allow testing
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Ikev2SessionCreator {
+ /** Creates a IKE session */
+ public IkeSession createIkeSession(
+ @NonNull Context context,
+ @NonNull IkeSessionParams ikeSessionParams,
+ @NonNull ChildSessionParams firstChildSessionParams,
+ @NonNull Executor userCbExecutor,
+ @NonNull IkeSessionCallback ikeSessionCallback,
+ @NonNull ChildSessionCallback firstChildSessionCallback) {
+ return new IkeSession(
+ context,
+ ikeSessionParams,
+ firstChildSessionParams,
+ userCbExecutor,
+ ikeSessionCallback,
+ firstChildSessionCallback);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
new file mode 100644
index 000000000000..33fc32b78df7
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -0,0 +1,390 @@
+/*
+ * 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.connectivity;
+
+import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_1024_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_192;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1;
+
+import android.annotation.NonNull;
+import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
+import android.net.Network;
+import android.net.RouteInfo;
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.util.IpRange;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.HexDump;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Utility class to build and convert IKEv2/IPsec parameters.
+ *
+ * @hide
+ */
+public class VpnIkev2Utils {
+ static IkeSessionParams buildIkeSessionParams(
+ @NonNull Ikev2VpnProfile profile, @NonNull UdpEncapsulationSocket socket) {
+ // TODO(b/149356682): Update this based on new IKE API. Only numeric addresses supported
+ // until then. All others throw IAE (caught by caller).
+ final InetAddress serverAddr = InetAddresses.parseNumericAddress(profile.getServerAddr());
+ final IkeIdentification localId = parseIkeIdentification(profile.getUserIdentity());
+ final IkeIdentification remoteId = parseIkeIdentification(profile.getServerAddr());
+
+ // TODO(b/149356682): Update this based on new IKE API.
+ final IkeSessionParams.Builder ikeOptionsBuilder =
+ new IkeSessionParams.Builder()
+ .setServerAddress(serverAddr)
+ .setUdpEncapsulationSocket(socket)
+ .setLocalIdentification(localId)
+ .setRemoteIdentification(remoteId);
+ setIkeAuth(profile, ikeOptionsBuilder);
+
+ for (final IkeSaProposal ikeProposal : getIkeSaProposals()) {
+ ikeOptionsBuilder.addSaProposal(ikeProposal);
+ }
+
+ return ikeOptionsBuilder.build();
+ }
+
+ static ChildSessionParams buildChildSessionParams() {
+ final TunnelModeChildSessionParams.Builder childOptionsBuilder =
+ new TunnelModeChildSessionParams.Builder();
+
+ for (final ChildSaProposal childProposal : getChildSaProposals()) {
+ childOptionsBuilder.addSaProposal(childProposal);
+ }
+
+ childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET);
+ childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET6);
+ childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET);
+ childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET6);
+
+ return childOptionsBuilder.build();
+ }
+
+ private static void setIkeAuth(
+ @NonNull Ikev2VpnProfile profile, @NonNull IkeSessionParams.Builder builder) {
+ switch (profile.getType()) {
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+ final EapSessionConfig eapConfig =
+ new EapSessionConfig.Builder()
+ .setEapMsChapV2Config(profile.getUsername(), profile.getPassword())
+ .build();
+ builder.setAuthEap(profile.getServerRootCaCert(), eapConfig);
+ break;
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+ builder.setAuthPsk(profile.getPresharedKey());
+ break;
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+ builder.setAuthDigitalSignature(
+ profile.getServerRootCaCert(),
+ profile.getUserCert(),
+ profile.getRsaPrivateKey());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown auth method set");
+ }
+ }
+
+ private static List<IkeSaProposal> getIkeSaProposals() {
+ // TODO: filter this based on allowedAlgorithms
+ final List<IkeSaProposal> proposals = new ArrayList<>();
+
+ // Encryption Algorithms: Currently only AES_CBC is supported.
+ final IkeSaProposal.Builder normalModeBuilder = new IkeSaProposal.Builder();
+
+ // Currently only AES_CBC is supported.
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+ // Authentication/Integrity Algorithms
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_AES_XCBC_96);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+ // Add AEAD options
+ final IkeSaProposal.Builder aeadBuilder = new IkeSaProposal.Builder();
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+ // Add dh, prf for both builders
+ for (final IkeSaProposal.Builder builder : Arrays.asList(normalModeBuilder, aeadBuilder)) {
+ builder.addDhGroup(DH_GROUP_2048_BIT_MODP);
+ builder.addDhGroup(DH_GROUP_1024_BIT_MODP);
+ builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC);
+ builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+ }
+
+ proposals.add(normalModeBuilder.build());
+ proposals.add(aeadBuilder.build());
+ return proposals;
+ }
+
+ private static List<ChildSaProposal> getChildSaProposals() {
+ // TODO: filter this based on allowedAlgorithms
+ final List<ChildSaProposal> proposals = new ArrayList<>();
+
+ // Add non-AEAD options
+ final ChildSaProposal.Builder normalModeBuilder = new ChildSaProposal.Builder();
+
+ // Encryption Algorithms: Currently only AES_CBC is supported.
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+ // Authentication/Integrity Algorithms
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+ // Add AEAD options
+ final ChildSaProposal.Builder aeadBuilder = new ChildSaProposal.Builder();
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+ proposals.add(normalModeBuilder.build());
+ proposals.add(aeadBuilder.build());
+ return proposals;
+ }
+
+ static class IkeSessionCallbackImpl implements IkeSessionCallback {
+ private final String mTag;
+ private final Vpn.IkeV2VpnRunnerCallback mCallback;
+ private final Network mNetwork;
+
+ IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+ mTag = tag;
+ mCallback = callback;
+ mNetwork = network;
+ }
+
+ @Override
+ public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
+ Log.d(mTag, "IkeOpened for network " + mNetwork);
+ // Nothing to do here.
+ }
+
+ @Override
+ public void onClosed() {
+ Log.d(mTag, "IkeClosed for network " + mNetwork);
+ mCallback.onSessionLost(mNetwork); // Server requested session closure. Retry?
+ }
+
+ @Override
+ public void onClosedExceptionally(@NonNull IkeException exception) {
+ Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
+ mCallback.onSessionLost(mNetwork);
+ }
+
+ @Override
+ public void onError(@NonNull IkeProtocolException exception) {
+ Log.d(mTag, "IkeError for network " + mNetwork, exception);
+ // Non-fatal, log and continue.
+ }
+ }
+
+ static class ChildSessionCallbackImpl implements ChildSessionCallback {
+ private final String mTag;
+ private final Vpn.IkeV2VpnRunnerCallback mCallback;
+ private final Network mNetwork;
+
+ ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+ mTag = tag;
+ mCallback = callback;
+ mNetwork = network;
+ }
+
+ @Override
+ public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
+ Log.d(mTag, "ChildOpened for network " + mNetwork);
+ mCallback.onChildOpened(mNetwork, childConfig);
+ }
+
+ @Override
+ public void onClosed() {
+ Log.d(mTag, "ChildClosed for network " + mNetwork);
+ mCallback.onSessionLost(mNetwork);
+ }
+
+ @Override
+ public void onClosedExceptionally(@NonNull IkeException exception) {
+ Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
+ mCallback.onSessionLost(mNetwork);
+ }
+
+ @Override
+ public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
+ Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; network " + mNetwork);
+ mCallback.onChildTransformCreated(mNetwork, transform, direction);
+ }
+
+ @Override
+ public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
+ // Nothing to be done; no references to the IpSecTransform are held by the
+ // Ikev2VpnRunner (or this callback class), and this transform will be closed by the
+ // IKE library.
+ Log.d(mTag,
+ "ChildTransformDeleted; Direction: " + direction + "; for network " + mNetwork);
+ }
+ }
+
+ static class Ikev2VpnNetworkCallback extends NetworkCallback {
+ private final String mTag;
+ private final Vpn.IkeV2VpnRunnerCallback mCallback;
+
+ Ikev2VpnNetworkCallback(String tag, Vpn.IkeV2VpnRunnerCallback callback) {
+ mTag = tag;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ Log.d(mTag, "Starting IKEv2/IPsec session on new network: " + network);
+ mCallback.onDefaultNetworkChanged(network);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ Log.d(mTag, "Tearing down; lost network: " + network);
+ mCallback.onSessionLost(network);
+ }
+ }
+
+ /**
+ * Identity parsing logic using similar logic to open source implementations of IKEv2
+ *
+ * <p>This method does NOT support using type-prefixes (eg 'fqdn:' or 'keyid'), or ASN.1 encoded
+ * identities.
+ */
+ private static IkeIdentification parseIkeIdentification(@NonNull String identityStr) {
+ // TODO: Add identity formatting to public API javadocs.
+ if (identityStr.contains("@")) {
+ if (identityStr.startsWith("@#")) {
+ // KEY_ID
+ final String hexStr = identityStr.substring(2);
+ return new IkeKeyIdIdentification(HexDump.hexStringToByteArray(hexStr));
+ } else if (identityStr.startsWith("@@")) {
+ // RFC822 (USER_FQDN)
+ return new IkeRfc822AddrIdentification(identityStr.substring(2));
+ } else if (identityStr.startsWith("@")) {
+ // FQDN
+ return new IkeFqdnIdentification(identityStr.substring(1));
+ } else {
+ // RFC822 (USER_FQDN)
+ return new IkeRfc822AddrIdentification(identityStr);
+ }
+ } else if (InetAddresses.isNumericAddress(identityStr)) {
+ final InetAddress addr = InetAddresses.parseNumericAddress(identityStr);
+ if (addr instanceof Inet4Address) {
+ // IPv4
+ return new IkeIpv4AddrIdentification((Inet4Address) addr);
+ } else if (addr instanceof Inet6Address) {
+ // IPv6
+ return new IkeIpv6AddrIdentification((Inet6Address) addr);
+ } else {
+ throw new IllegalArgumentException("IP version not supported");
+ }
+ } else {
+ if (identityStr.contains(":")) {
+ // KEY_ID
+ return new IkeKeyIdIdentification(identityStr.getBytes());
+ } else {
+ // FQDN
+ return new IkeFqdnIdentification(identityStr);
+ }
+ }
+ }
+
+ static Collection<RouteInfo> getRoutesFromTrafficSelectors(
+ List<IkeTrafficSelector> trafficSelectors) {
+ final HashSet<RouteInfo> routes = new HashSet<>();
+
+ for (final IkeTrafficSelector selector : trafficSelectors) {
+ for (final IpPrefix prefix :
+ new IpRange(selector.startingAddress, selector.endingAddress).asIpPrefixes()) {
+ routes.add(new RouteInfo(prefix, null));
+ }
+ }
+
+ return routes;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 7c2ec78c1cbc..d9e30250ba2d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -105,32 +105,57 @@ import java.util.stream.Collectors;
*/
public class HdmiControlService extends SystemService {
private static final String TAG = "HdmiControlService";
- private final Locale HONG_KONG = new Locale("zh", "HK");
- private final Locale MACAU = new Locale("zh", "MO");
+ private static final Locale HONG_KONG = new Locale("zh", "HK");
+ private static final Locale MACAU = new Locale("zh", "MO");
- private static final Map<String, String> mTerminologyToBibliographicMap;
- static {
- mTerminologyToBibliographicMap = new HashMap<>();
+ private static final Map<String, String> sTerminologyToBibliographicMap =
+ createsTerminologyToBibliographicMap();
+
+ private static Map<String, String> createsTerminologyToBibliographicMap() {
+ Map<String, String> temp = new HashMap<>();
// NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE)
- mTerminologyToBibliographicMap.put("sqi", "alb"); // Albanian
- mTerminologyToBibliographicMap.put("hye", "arm"); // Armenian
- mTerminologyToBibliographicMap.put("eus", "baq"); // Basque
- mTerminologyToBibliographicMap.put("mya", "bur"); // Burmese
- mTerminologyToBibliographicMap.put("ces", "cze"); // Czech
- mTerminologyToBibliographicMap.put("nld", "dut"); // Dutch
- mTerminologyToBibliographicMap.put("kat", "geo"); // Georgian
- mTerminologyToBibliographicMap.put("deu", "ger"); // German
- mTerminologyToBibliographicMap.put("ell", "gre"); // Greek
- mTerminologyToBibliographicMap.put("fra", "fre"); // French
- mTerminologyToBibliographicMap.put("isl", "ice"); // Icelandic
- mTerminologyToBibliographicMap.put("mkd", "mac"); // Macedonian
- mTerminologyToBibliographicMap.put("mri", "mao"); // Maori
- mTerminologyToBibliographicMap.put("msa", "may"); // Malay
- mTerminologyToBibliographicMap.put("fas", "per"); // Persian
- mTerminologyToBibliographicMap.put("ron", "rum"); // Romanian
- mTerminologyToBibliographicMap.put("slk", "slo"); // Slovak
- mTerminologyToBibliographicMap.put("bod", "tib"); // Tibetan
- mTerminologyToBibliographicMap.put("cym", "wel"); // Welsh
+ temp.put("sqi", "alb"); // Albanian
+ temp.put("hye", "arm"); // Armenian
+ temp.put("eus", "baq"); // Basque
+ temp.put("mya", "bur"); // Burmese
+ temp.put("ces", "cze"); // Czech
+ temp.put("nld", "dut"); // Dutch
+ temp.put("kat", "geo"); // Georgian
+ temp.put("deu", "ger"); // German
+ temp.put("ell", "gre"); // Greek
+ temp.put("fra", "fre"); // French
+ temp.put("isl", "ice"); // Icelandic
+ temp.put("mkd", "mac"); // Macedonian
+ temp.put("mri", "mao"); // Maori
+ temp.put("msa", "may"); // Malay
+ temp.put("fas", "per"); // Persian
+ temp.put("ron", "rum"); // Romanian
+ temp.put("slk", "slo"); // Slovak
+ temp.put("bod", "tib"); // Tibetan
+ temp.put("cym", "wel"); // Welsh
+ return Collections.unmodifiableMap(temp);
+ }
+
+ @VisibleForTesting static String localeToMenuLanguage(Locale locale) {
+ if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
+ // Android always returns "zho" for all Chinese variants.
+ // Use "bibliographic" code defined in CEC639-2 for traditional
+ // Chinese used in Taiwan/Hong Kong/Macau.
+ return "chi";
+ } else {
+ String language = locale.getISO3Language();
+
+ // locale.getISO3Language() returns terminology code and need to
+ // send it as bibliographic code instead since the Bibliographic
+ // codes of ISO/FDIS 639-2 shall be used.
+ // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
+ // But, as it depends on the locale, is not handled here.
+ if (sTerminologyToBibliographicMap.containsKey(language)) {
+ language = sTerminologyToBibliographicMap.get(language);
+ }
+
+ return language;
+ }
}
static final String PERMISSION = "android.permission.HDMI_CEC";
@@ -208,8 +233,8 @@ public class HdmiControlService extends SystemService {
}
break;
case Intent.ACTION_CONFIGURATION_CHANGED:
- String language = getMenuLanguage();
- if (!mLanguage.equals(language)) {
+ String language = HdmiControlService.localeToMenuLanguage(Locale.getDefault());
+ if (!mMenuLanguage.equals(language)) {
onLanguageChanged(language);
}
break;
@@ -221,28 +246,6 @@ public class HdmiControlService extends SystemService {
}
}
- private String getMenuLanguage() {
- Locale locale = Locale.getDefault();
- if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
- // Android always returns "zho" for all Chinese variants.
- // Use "bibliographic" code defined in CEC639-2 for traditional
- // Chinese used in Taiwan/Hong Kong/Macau.
- return "chi";
- } else {
- String language = locale.getISO3Language();
-
- // locale.getISO3Language() returns terminology code and need to
- // send it as bibliographic code instead since the Bibliographic
- // codes of ISO/FDIS 639-2 shall be used.
- // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
- // But, as it depends on the locale, is not handled here.
- if (mTerminologyToBibliographicMap.containsKey(language)) {
- language = mTerminologyToBibliographicMap.get(language);
- }
-
- return language;
- }
- }
}
// A thread to handle synchronous IO of CEC and MHL control service.
@@ -339,7 +342,7 @@ public class HdmiControlService extends SystemService {
private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
@ServiceThreadOnly
- private String mLanguage = Locale.getDefault().getISO3Language();
+ private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
@ServiceThreadOnly
private boolean mStandbyMessageReceived = false;
@@ -759,7 +762,7 @@ public class HdmiControlService extends SystemService {
private void initializeCec(int initiatedBy) {
mAddressAllocated = false;
mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
- mCecController.setLanguage(mLanguage);
+ mCecController.setLanguage(mMenuLanguage);
initializeLocalDevices(initiatedBy);
}
@@ -2818,7 +2821,7 @@ public class HdmiControlService extends SystemService {
@ServiceThreadOnly
private void onLanguageChanged(String language) {
assertRunOnServiceThread();
- mLanguage = language;
+ mMenuLanguage = language;
if (isTvDeviceEnabled()) {
tv().broadcastMenuLanguage(language);
@@ -2826,10 +2829,19 @@ public class HdmiControlService extends SystemService {
}
}
+ /**
+ * Gets the CEC menu language.
+ *
+ * <p>This is the ISO/FDIS 639-2 3 letter language code sent in the CEC message @{code <Set Menu
+ * Language>}.
+ * See HDMI 1.4b section CEC 13.6.2
+ *
+ * @see {@link Locale#getISO3Language()}
+ */
@ServiceThreadOnly
String getLanguage() {
assertRunOnServiceThread();
- return mLanguage;
+ return mMenuLanguage;
}
private void disableDevices(PendingActionClearedCallback callback) {
@@ -2838,7 +2850,6 @@ public class HdmiControlService extends SystemService {
device.disableDevice(mStandbyMessageReceived, callback);
}
}
-
mMhlController.clearAllLocalDevices();
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 916b63bee0e8..d8cab82cfb06 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -932,7 +932,7 @@ public class NotificationManagerService extends SystemService {
StatusBarNotification sbn = r.getSbn();
cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
sbn.getId(), Notification.FLAG_AUTO_CANCEL,
- FLAG_FOREGROUND_SERVICE, false, r.getUserId(),
+ FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, false, r.getUserId(),
REASON_CLICK, nv.rank, nv.count, null);
nv.recycle();
reportUserInteraction(r);
@@ -1055,7 +1055,7 @@ public class NotificationManagerService extends SystemService {
if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key);
reportSeen(r);
}
- r.setVisibility(true, nv.rank, nv.count);
+ r.setVisibility(true, nv.rank, nv.count, mNotificationRecordLogger);
mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), true);
boolean isHun = (nv.location
== NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
@@ -1074,7 +1074,7 @@ public class NotificationManagerService extends SystemService {
for (NotificationVisibility nv : noLongerVisibleKeys) {
NotificationRecord r = mNotificationsByKey.get(nv.key);
if (r == null) continue;
- r.setVisibility(false, nv.rank, nv.count);
+ r.setVisibility(false, nv.rank, nv.count, mNotificationRecordLogger);
mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), false);
nv.recycle();
}
@@ -6468,7 +6468,7 @@ public class NotificationManagerService extends SystemService {
mUsageStats.registerPostedByApp(r);
r.setInterruptive(isVisuallyInterruptive(null, r));
} else {
- old = mNotificationList.get(index);
+ old = mNotificationList.get(index); // Potentially *changes* old
mNotificationList.set(index, r);
mUsageStats.registerUpdatedByApp(r, old);
// Make sure we don't lose the foreground service state.
@@ -6537,7 +6537,7 @@ public class NotificationManagerService extends SystemService {
maybeRecordInterruptionLocked(r);
// Log event to statsd
- mNotificationRecordLogger.logNotificationReported(r, old, position,
+ mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position,
buzzBeepBlinkLoggingCode);
} finally {
int N = mEnqueuedNotifications.size();
@@ -7986,7 +7986,8 @@ public class NotificationManagerService extends SystemService {
FlagChecker flagChecker = (int flags) -> {
int flagsToCheck = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
- if (REASON_LISTENER_CANCEL_ALL == reason) {
+ if (REASON_LISTENER_CANCEL_ALL == reason
+ || REASON_CANCEL_ALL == reason) {
flagsToCheck |= FLAG_BUBBLE;
}
if ((flags & flagsToCheck) != 0) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 4785da9a5922..0ada58e1ce16 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -909,7 +909,8 @@ public final class NotificationRecord {
/**
* Set the visibility of the notification.
*/
- public void setVisibility(boolean visible, int rank, int count) {
+ public void setVisibility(boolean visible, int rank, int count,
+ NotificationRecordLogger notificationRecordLogger) {
final long now = System.currentTimeMillis();
mVisibleSinceMs = visible ? now : mVisibleSinceMs;
stats.onVisibilityChanged(visible);
@@ -927,6 +928,7 @@ public final class NotificationRecord {
getFreshnessMs(now),
0, // exposure time
rank);
+ notificationRecordLogger.logNotificationVisibility(this, visible);
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 2f7854226c5c..eaca066f026f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -41,13 +41,14 @@ import java.util.Objects;
public interface NotificationRecordLogger {
/**
- * Logs a NotificationReported atom reflecting the posting or update of a notification.
+ * May log a NotificationReported atom reflecting the posting or update of a notification.
* @param r The new NotificationRecord. If null, no action is taken.
* @param old The previous NotificationRecord. Null if there was no previous record.
* @param position The position at which this notification is ranked.
* @param buzzBeepBlink Logging code reflecting whether this notification alerted the user.
*/
- void logNotificationReported(@Nullable NotificationRecord r, @Nullable NotificationRecord old,
+ void maybeLogNotificationPosted(@Nullable NotificationRecord r,
+ @Nullable NotificationRecord old,
int position, int buzzBeepBlink);
/**
@@ -62,6 +63,14 @@ public interface NotificationRecordLogger {
@NotificationStats.DismissalSurface int dismissalSurface);
/**
+ * Logs a notification visibility change event using UiEventReported (event ids from the
+ * NotificationEvents enum).
+ * @param r The NotificationRecord. If null, no action is taken.
+ * @param visible True if the notification became visible.
+ */
+ void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible);
+
+ /**
* The UiEvent enums that this class can log.
*/
enum NotificationReportedEvent implements UiEventLogger.UiEventEnum {
@@ -145,6 +154,7 @@ public interface NotificationRecordLogger {
@Override public int getId() {
return mId;
}
+
public static NotificationCancelledEvent fromCancelReason(
@NotificationListenerService.NotificationCancelReason int reason,
@NotificationStats.DismissalSurface int surface) {
@@ -191,6 +201,24 @@ public interface NotificationRecordLogger {
}
}
+ enum NotificationEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Notification became visible.")
+ NOTIFICATION_OPEN(197),
+ @UiEvent(doc = "Notification stopped being visible.")
+ NOTIFICATION_CLOSE(198);
+
+ private final int mId;
+ NotificationEvent(int id) {
+ mId = id;
+ }
+ @Override public int getId() {
+ return mId;
+ }
+
+ public static NotificationEvent fromVisibility(boolean visible) {
+ return visible ? NOTIFICATION_OPEN : NOTIFICATION_CLOSE;
+ }
+ }
/**
* A helper for extracting logging information from one or two NotificationRecords.
*/
@@ -209,7 +237,7 @@ public interface NotificationRecordLogger {
/**
* @return True if old is null, alerted, or important logged fields have changed.
*/
- boolean shouldLog(int buzzBeepBlink) {
+ boolean shouldLogReported(int buzzBeepBlink) {
if (r == null) {
return false;
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index bb23d1e876dc..9fcac257d328 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -26,13 +26,13 @@ import com.android.internal.util.FrameworkStatsLog;
*/
public class NotificationRecordLoggerImpl implements NotificationRecordLogger {
- UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+ private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
@Override
- public void logNotificationReported(NotificationRecord r, NotificationRecord old,
+ public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old,
int position, int buzzBeepBlink) {
NotificationRecordPair p = new NotificationRecordPair(r, old);
- if (!p.shouldLog(buzzBeepBlink)) {
+ if (!p.shouldLogReported(buzzBeepBlink)) {
return;
}
FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED,
@@ -66,8 +66,19 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger {
@Override
public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
- mUiEventLogger.logWithInstanceId(
- NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface),
- r.getUid(), r.getSbn().getPackageName(), r.getSbn().getInstanceId());
+ log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r);
+ }
+
+ @Override
+ public void logNotificationVisibility(NotificationRecord r, boolean visible) {
+ log(NotificationEvent.fromVisibility(visible), r);
+ }
+
+ void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
+ if (r == null) {
+ return;
+ }
+ mUiEventLogger.logWithInstanceId(event, r.getUid(), r.getSbn().getPackageName(),
+ r.getSbn().getInstanceId());
}
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 67a22d3e477c..39093aec07c0 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -866,6 +866,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
}
}
+ default int getMaxWindowLayer() {
+ return 35;
+ }
+
/**
* Return how to Z-order sub-windows in relation to the window they are attached to.
* Return positive to have them ordered in front, negative for behind.
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b3edc91a4129..0d365b16e228 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -102,6 +102,11 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
}
@Override
+ public String toString() {
+ return mName + "@" + System.identityHashCode(this);
+ }
+
+ @Override
public final void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) {
final long token = proto.start(fieldId);
super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 9e93e1455f2c..0ec0c7b53875 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-
import android.content.res.Resources;
import android.text.TextUtils;
@@ -43,7 +41,7 @@ public abstract class DisplayAreaPolicy {
/**
* The Tasks container. Tasks etc. are automatically added to this container.
*/
- protected final TaskContainers mTaskContainers;
+ protected final DisplayArea<? extends ActivityStack> mTaskContainers;
/**
* Construct a new {@link DisplayAreaPolicy}
@@ -58,7 +56,8 @@ public abstract class DisplayAreaPolicy {
*/
protected DisplayAreaPolicy(WindowManagerService wmService,
DisplayContent content, DisplayArea.Root root,
- DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) {
+ DisplayArea<? extends WindowContainer> imeContainer,
+ DisplayArea<? extends ActivityStack> taskContainers) {
mWmService = wmService;
mContent = content;
mRoot = root;
@@ -83,67 +82,15 @@ public abstract class DisplayAreaPolicy {
*/
public abstract void addWindow(WindowToken token);
- /**
- * Default policy that has no special features.
- */
- public static class Default extends DisplayAreaPolicy {
-
- public Default(WindowManagerService wmService, DisplayContent content,
- DisplayArea.Root root,
+ /** Provider for platform-default display area policy. */
+ static final class DefaultProvider implements DisplayAreaPolicy.Provider {
+ @Override
+ public DisplayAreaPolicy instantiate(WindowManagerService wmService,
+ DisplayContent content, DisplayArea.Root root,
DisplayArea<? extends WindowContainer> imeContainer,
TaskContainers taskContainers) {
- super(wmService, content, root, imeContainer, taskContainers);
- }
-
- private final DisplayArea.Tokens mBelow = new DisplayArea.Tokens(mWmService,
- DisplayArea.Type.BELOW_TASKS, "BelowTasks");
- private final DisplayArea<DisplayArea> mAbove = new DisplayArea<>(mWmService,
- DisplayArea.Type.ABOVE_TASKS, "AboveTasks");
- private final DisplayArea.Tokens mAboveBelowIme = new DisplayArea.Tokens(mWmService,
- DisplayArea.Type.ABOVE_TASKS, "AboveTasksBelowIme");
- private final DisplayArea.Tokens mAboveAboveIme = new DisplayArea.Tokens(mWmService,
- DisplayArea.Type.ABOVE_TASKS, "AboveTasksAboveIme");
-
- @Override
- public void attachDisplayAreas() {
- mRoot.addChild(mBelow, 0);
- mRoot.addChild(mTaskContainers, 1);
- mRoot.addChild(mAbove, 2);
-
- mAbove.addChild(mAboveBelowIme, 0);
- mAbove.addChild(mImeContainer, 1);
- mAbove.addChild(mAboveAboveIme, 2);
- }
-
- @Override
- public void addWindow(WindowToken token) {
- switch (DisplayArea.Type.typeOf(token)) {
- case ABOVE_TASKS:
- if (token.getWindowLayerFromType()
- < mWmService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)) {
- mAboveBelowIme.addChild(token);
- } else {
- mAboveAboveIme.addChild(token);
- }
- break;
- case BELOW_TASKS:
- mBelow.addChild(token);
- break;
- default:
- throw new IllegalArgumentException("don't know how to sort " + token);
- }
- }
-
- /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */
- static class Provider implements DisplayAreaPolicy.Provider {
- @Override
- public DisplayAreaPolicy instantiate(WindowManagerService wmService,
- DisplayContent content, DisplayArea.Root root,
- DisplayArea<? extends WindowContainer> imeContainer,
- TaskContainers taskContainers) {
- return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer,
- taskContainers);
- }
+ return new DisplayAreaPolicyBuilder()
+ .build(wmService, content, root, imeContainer, taskContainers);
}
}
@@ -172,7 +119,7 @@ public abstract class DisplayAreaPolicy {
String name = res.getString(
com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider);
if (TextUtils.isEmpty(name)) {
- return new DisplayAreaPolicy.Default.Provider();
+ return new DisplayAreaPolicy.DefaultProvider();
}
try {
return (Provider) Class.forName(name).newInstance();
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
new file mode 100644
index 000000000000..885456a8488c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -0,0 +1,371 @@
+/*
+ * 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.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A builder for instantiating a complex {@link DisplayAreaPolicy}
+ *
+ * <p>Given a set of features (that each target a set of window types), it builds the necessary
+ * DisplayArea hierarchy.
+ *
+ * <p>Example: <br />
+ *
+ * <pre>
+ * // Feature for targeting everything below the magnification overlay:
+ * new DisplayAreaPolicyBuilder(...)
+ * .addFeature(new Feature.Builder(..., "Magnification")
+ * .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
+ * .build())
+ * .build(...)
+ *
+ * // Builds a policy with the following hierarchy:
+ * - DisplayArea.Root
+ * - Magnification
+ * - DisplayArea.Tokens (Wallpapers are attached here)
+ * - TaskContainers
+ * - DisplayArea.Tokens (windows above Tasks up to IME are attached here)
+ * - ImeContainers
+ * - DisplayArea.Tokens (windows above IME up to TYPE_ACCESSIBILITY_OVERLAY attached here)
+ * - DisplayArea.Tokens (TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY and up are attached here)
+ *
+ * </pre>
+ *
+ * // TODO(display-area): document more complex scenarios where we need multiple areas per feature.
+ */
+class DisplayAreaPolicyBuilder {
+
+ private final ArrayList<Feature> mFeatures = new ArrayList<>();
+
+ /**
+ * A feature that requires {@link DisplayArea DisplayArea(s)}.
+ */
+ static class Feature {
+ private final String mName;
+ private final boolean[] mWindowLayers;
+
+ private Feature(String name, boolean[] windowLayers) {
+ mName = name;
+ mWindowLayers = windowLayers;
+ }
+
+ static class Builder {
+ private final WindowManagerPolicy mPolicy;
+ private final String mName;
+ private final boolean[] mLayers;
+
+ /**
+ * Build a new feature that applies to a set of window types as specified by the builder
+ * methods.
+ *
+ * <p>The set of types is updated iteratively in the order of the method invocations.
+ * For example, {@code all().except(TYPE_STATUS_BAR)} expresses that a feature should
+ * apply to all types except TYPE_STATUS_BAR.
+ *
+ * The builder starts out with the feature not applying to any types.
+ *
+ * @param name the name of the feature.
+ */
+ Builder(WindowManagerPolicy policy, String name) {
+ mPolicy = policy;
+ mName = name;
+ mLayers = new boolean[mPolicy.getMaxWindowLayer()];
+ }
+
+ /**
+ * Set that the feature applies to all window types.
+ */
+ Builder all() {
+ Arrays.fill(mLayers, true);
+ return this;
+ }
+
+ /**
+ * Set that the feature applies to the given window types.
+ */
+ Builder and(int... types) {
+ for (int i = 0; i < types.length; i++) {
+ int type = types[i];
+ set(type, true);
+ }
+ return this;
+ }
+
+ /**
+ * Set that the feature does not apply to the given window types.
+ */
+ Builder except(int... types) {
+ for (int i = 0; i < types.length; i++) {
+ int type = types[i];
+ set(type, false);
+ }
+ return this;
+ }
+
+ /**
+ * Set that the feature applies window types that are layerd at or below the layer of
+ * the given window type.
+ */
+ Builder upTo(int typeInclusive) {
+ final int max = layerFromType(typeInclusive, false);
+ for (int i = 0; i < max; i++) {
+ mLayers[i] = true;
+ }
+ set(typeInclusive, true);
+ return this;
+ }
+
+ Feature build() {
+ return new Feature(mName, mLayers.clone());
+ }
+
+ private void set(int type, boolean value) {
+ mLayers[layerFromType(type, true)] = value;
+ if (type == TYPE_APPLICATION_OVERLAY) {
+ mLayers[layerFromType(type, true)] = value;
+ mLayers[layerFromType(TYPE_SYSTEM_ALERT, false)] = value;
+ mLayers[layerFromType(TYPE_SYSTEM_OVERLAY, false)] = value;
+ mLayers[layerFromType(TYPE_SYSTEM_ERROR, false)] = value;
+ }
+ }
+
+ private int layerFromType(int type, boolean internalWindows) {
+ return mPolicy.getWindowLayerFromTypeLw(type, internalWindows);
+ }
+ }
+ }
+
+ static class Result extends DisplayAreaPolicy {
+ private static final int LEAF_TYPE_TASK_CONTAINERS = 1;
+ private static final int LEAF_TYPE_IME_CONTAINERS = 2;
+ private static final int LEAF_TYPE_TOKENS = 0;
+
+ private final int mMaxWindowLayer = mWmService.mPolicy.getMaxWindowLayer();
+
+ private final ArrayList<Feature> mFeatures;
+ private final Map<Feature, List<DisplayArea<? extends WindowContainer>>> mAreas;
+ private final DisplayArea.Tokens[] mAreaForLayer = new DisplayArea.Tokens[mMaxWindowLayer];
+
+ Result(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root,
+ DisplayArea<? extends WindowContainer> imeContainer,
+ DisplayArea<? extends ActivityStack> taskStacks, ArrayList<Feature> features) {
+ super(wmService, content, root, imeContainer, taskStacks);
+ mFeatures = features;
+ mAreas = new HashMap<>(features.size());
+ for (int i = 0; i < mFeatures.size(); i++) {
+ mAreas.put(mFeatures.get(i), new ArrayList<>());
+ }
+ }
+
+ @Override
+ public void attachDisplayAreas() {
+ // This method constructs the layer hierarchy with the following properties:
+ // (1) Every feature maps to a set of DisplayAreas
+ // (2) After adding a window, for every feature the window's type belongs to,
+ // it is a descendant of one of the corresponding DisplayAreas of the feature.
+ // (3) Z-order is maintained, i.e. if z-range(area) denotes the set of layers of windows
+ // within a DisplayArea:
+ // for every pair of DisplayArea siblings (a,b), where a is below b, it holds that
+ // max(z-range(a)) <= min(z-range(b))
+ //
+ // The algorithm below iteratively creates such a hierarchy:
+ // - Initially, all windows are attached to the root.
+ // - For each feature we create a set of DisplayAreas, by looping over the layers
+ // - if the feature does apply to the current layer, we need to find a DisplayArea
+ // for it to satisfy (2)
+ // - we can re-use the previous layer's area if:
+ // the current feature also applies to the previous layer, (to satisfy (3))
+ // and the last feature that applied to the previous layer is the same as
+ // the last feature that applied to the current layer (to satisfy (2))
+ // - otherwise we create a new DisplayArea below the last feature that applied
+ // to the current layer
+
+
+ PendingArea[] areaForLayer = new PendingArea[mMaxWindowLayer];
+ final PendingArea root = new PendingArea(null, 0, null);
+ Arrays.fill(areaForLayer, root);
+
+ final int size = mFeatures.size();
+ for (int i = 0; i < size; i++) {
+ PendingArea featureArea = null;
+ for (int layer = 0; layer < mMaxWindowLayer; layer++) {
+ final Feature feature = mFeatures.get(i);
+ if (feature.mWindowLayers[layer]) {
+ if (featureArea == null || featureArea.mParent != areaForLayer[layer]) {
+ // No suitable DisplayArea - create a new one under the previous area
+ // for this layer.
+ featureArea = new PendingArea(feature, layer, areaForLayer[layer]);
+ areaForLayer[layer].mChildren.add(featureArea);
+ }
+ areaForLayer[layer] = featureArea;
+ } else {
+ featureArea = null;
+ }
+ }
+ }
+
+ PendingArea leafArea = null;
+ int leafType = LEAF_TYPE_TOKENS;
+ for (int layer = 0; layer < mMaxWindowLayer; layer++) {
+ int type = typeOfLayer(mWmService.mPolicy, layer);
+ if (leafArea == null || leafArea.mParent != areaForLayer[layer]
+ || type != leafType) {
+ leafArea = new PendingArea(null, layer, areaForLayer[layer]);
+ areaForLayer[layer].mChildren.add(leafArea);
+ leafType = type;
+ if (leafType == LEAF_TYPE_TASK_CONTAINERS) {
+ leafArea.mExisting = mTaskContainers;
+ } else if (leafType == LEAF_TYPE_IME_CONTAINERS) {
+ leafArea.mExisting = mImeContainer;
+ }
+ }
+ leafArea.mMaxLayer = layer;
+ }
+ root.computeMaxLayer();
+ root.instantiateChildren(mRoot, mAreaForLayer, 0, mAreas);
+ }
+
+ @Override
+ public void addWindow(WindowToken token) {
+ DisplayArea.Tokens area = findAreaForToken(token);
+ area.addChild(token);
+ }
+
+ @VisibleForTesting
+ DisplayArea.Tokens findAreaForToken(WindowToken token) {
+ int windowLayerFromType = token.getWindowLayerFromType();
+ if (windowLayerFromType == APPLICATION_LAYER) {
+ // TODO(display-area): Better handle AboveAppWindows in APPLICATION_LAYER
+ windowLayerFromType += 1;
+ } else if (token.mRoundedCornerOverlay) {
+ windowLayerFromType = mMaxWindowLayer - 1;
+ }
+ return mAreaForLayer[windowLayerFromType];
+ }
+
+ public List<DisplayArea<? extends WindowContainer>> getDisplayAreas(Feature feature) {
+ return mAreas.get(feature);
+ }
+
+ private static int typeOfLayer(WindowManagerPolicy policy, int layer) {
+ if (layer == APPLICATION_LAYER) {
+ return LEAF_TYPE_TASK_CONTAINERS;
+ } else if (layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)
+ || layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD_DIALOG)) {
+ return LEAF_TYPE_IME_CONTAINERS;
+ } else {
+ return LEAF_TYPE_TOKENS;
+ }
+ }
+ }
+
+ DisplayAreaPolicyBuilder addFeature(Feature feature) {
+ mFeatures.add(feature);
+ return this;
+ }
+
+ Result build(WindowManagerService wmService,
+ DisplayContent content, DisplayArea.Root root,
+ DisplayArea<? extends WindowContainer> imeContainer,
+ DisplayArea<? extends ActivityStack> taskContainers) {
+
+ return new Result(wmService, content, root, imeContainer, taskContainers, new ArrayList<>(
+ mFeatures));
+ }
+
+ static class PendingArea {
+ final int mMinLayer;
+ final ArrayList<PendingArea> mChildren = new ArrayList<>();
+ final Feature mFeature;
+ final PendingArea mParent;
+ int mMaxLayer;
+ DisplayArea mExisting;
+
+ PendingArea(Feature feature,
+ int minLayer,
+ PendingArea parent) {
+ mMinLayer = minLayer;
+ mFeature = feature;
+ mParent = parent;
+ }
+
+ int computeMaxLayer() {
+ for (int i = 0; i < mChildren.size(); i++) {
+ mMaxLayer = Math.max(mMaxLayer, mChildren.get(i).computeMaxLayer());
+ }
+ return mMaxLayer;
+ }
+
+ void instantiateChildren(DisplayArea<DisplayArea> parent,
+ DisplayArea.Tokens[] areaForLayer, int level, Map<Feature, List<DisplayArea<?
+ extends WindowContainer>>> areas) {
+ mChildren.sort(Comparator.comparingInt(pendingArea -> pendingArea.mMinLayer));
+ for (int i = 0; i < mChildren.size(); i++) {
+ final PendingArea child = mChildren.get(i);
+ final DisplayArea area = child.createArea(parent, areaForLayer);
+ parent.addChild(area, WindowContainer.POSITION_TOP);
+ if (mFeature != null) {
+ areas.get(mFeature).add(area);
+ }
+ child.instantiateChildren(area, areaForLayer, level + 1, areas);
+ }
+ }
+
+ private DisplayArea createArea(DisplayArea<DisplayArea> parent,
+ DisplayArea.Tokens[] areaForLayer) {
+ if (mExisting != null) {
+ return mExisting;
+ }
+ DisplayArea.Type type;
+ if (mMinLayer > APPLICATION_LAYER) {
+ type = DisplayArea.Type.ABOVE_TASKS;
+ } else if (mMaxLayer < APPLICATION_LAYER) {
+ type = DisplayArea.Type.BELOW_TASKS;
+ } else {
+ type = DisplayArea.Type.ANY;
+ }
+ if (mFeature == null) {
+ final DisplayArea.Tokens leaf = new DisplayArea.Tokens(parent.mWmService, type,
+ "Leaf:" + mMinLayer + ":" + mMaxLayer);
+ for (int i = mMinLayer; i <= mMaxLayer; i++) {
+ areaForLayer[i] = leaf;
+ }
+ return leaf;
+ } else {
+ return new DisplayArea(parent.mWmService, type, mFeature.mName + ":"
+ + mMinLayer + ":" + mMaxLayer);
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java
new file mode 100644
index 000000000000..607cd816d7dd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Locale;
+
+/**
+ * Tests for static methods of {@link HdmiControlService} class.
+ */
+@SmallTest
+@RunWith(JUnit4.class)
+public class HdmiControlServiceStaticTest {
+
+ @Test
+ public void localToMenuLanguage_english() {
+ assertThat(HdmiControlService.localeToMenuLanguage(Locale.ENGLISH)).isEqualTo("eng");
+ }
+
+ @Test
+ public void localToMenuLanguage_german() {
+ assertThat(HdmiControlService.localeToMenuLanguage(Locale.GERMAN)).isEqualTo("ger");
+ }
+
+ @Test
+ public void localToMenuLanguage_taiwan() {
+ assertThat(HdmiControlService.localeToMenuLanguage(Locale.TAIWAN)).isEqualTo("chi");
+ }
+
+ @Test
+ public void localToMenuLanguage_macau() {
+ assertThat(HdmiControlService.localeToMenuLanguage(new Locale("zh", "MO"))).isEqualTo(
+ "chi");
+ }
+
+ @Test
+ public void localToMenuLanguage_hongkong() {
+ assertThat(HdmiControlService.localeToMenuLanguage(new Locale("zh", "HK"))).isEqualTo(
+ "chi");
+ }
+
+ @Test
+ public void localToMenuLanguage_chinese() {
+ assertThat(HdmiControlService.localeToMenuLanguage(Locale.CHINESE)).isEqualTo("zho");
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index e768205c2cf4..9e577636c1b3 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -145,6 +145,7 @@ public class AppStandbyControllerTests {
static class MyInjector extends AppStandbyController.Injector {
long mElapsedRealtime;
boolean mIsAppIdleEnabled = true;
+ boolean mIsCharging;
List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
boolean mDisplayOn;
DisplayManager.DisplayListener mDisplayListener;
@@ -179,6 +180,11 @@ public class AppStandbyControllerTests {
}
@Override
+ boolean isCharging() {
+ return mIsCharging;
+ }
+
+ @Override
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mPowerSaveWhitelistExceptIdle.contains(packageName);
}
@@ -281,6 +287,13 @@ public class AppStandbyControllerTests {
} catch (PackageManager.NameNotFoundException nnfe) {}
}
+ private void setChargingState(AppStandbyController controller, boolean charging) {
+ mInjector.mIsCharging = charging;
+ if (controller != null) {
+ controller.setChargingState(charging);
+ }
+ }
+
private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) {
mInjector.mIsAppIdleEnabled = enabled;
if (controller != null) {
@@ -297,6 +310,7 @@ public class AppStandbyControllerTests {
controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mInjector.setDisplayOn(false);
mInjector.setDisplayOn(true);
+ setChargingState(controller, false);
controller.checkIdleStates(USER_ID);
assertNotEquals(STANDBY_BUCKET_EXEMPTED,
controller.getAppStandbyBucket(PACKAGE_1, USER_ID,
@@ -324,6 +338,46 @@ public class AppStandbyControllerTests {
mInjector.mElapsedRealtime, false));
}
+ @Test
+ public void testIsAppIdle_Charging() throws Exception {
+ setChargingState(mController, false);
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_FORCED_BY_SYSTEM);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setChargingState(mController, true);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setChargingState(mController, false);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ }
+
+ @Test
+ public void testIsAppIdle_Enabled() throws Exception {
+ setChargingState(mController, false);
+ setAppIdleEnabled(mController, true);
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_FORCED_BY_SYSTEM);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setAppIdleEnabled(mController, false);
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setAppIdleEnabled(mController, true);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ }
+
private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
mInjector.mElapsedRealtime = elapsedTime;
controller.checkIdleStates(USER_ID);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index a0f7f5b801ec..92d47c3a57b6 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -51,8 +51,6 @@ import static android.os.Build.VERSION_CODES.P;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
@@ -1144,14 +1142,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testEnqueueNotificationWithTag_WritesExpectedLog() throws Exception {
+ public void testEnqueueNotificationWithTag_WritesExpectedLogs() throws Exception {
final String tag = "testEnqueueNotificationWithTag_WritesExpectedLog";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(), 0);
waitForIdle();
assertEquals(1, mNotificationRecordLogger.getCalls().size());
+
NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0);
- assertTrue(call.shouldLog);
+ assertTrue(call.shouldLogReported);
assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
call.event);
assertNotNull(call.r);
@@ -1161,7 +1160,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertEquals(PKG, call.r.getSbn().getPackageName());
assertEquals(0, call.r.getSbn().getId());
assertEquals(tag, call.r.getSbn().getTag());
- assertNotNull(call.r.getSbn().getInstanceId());
assertEquals(0, call.getInstanceId()); // Fake instance IDs are assigned in order
}
@@ -1180,13 +1178,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
waitForIdle();
assertEquals(2, mNotificationRecordLogger.getCalls().size());
- assertTrue(mNotificationRecordLogger.get(0).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
mNotificationRecordLogger.get(0).event);
assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
- assertTrue(mNotificationRecordLogger.get(1).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(1).shouldLogReported);
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED,
mNotificationRecordLogger.get(1).event);
@@ -1195,16 +1193,37 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates() throws Exception {
- final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates";
+ public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate() throws Exception {
+ final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate";
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(), 0);
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(), 0);
waitForIdle();
assertEquals(2, mNotificationRecordLogger.getCalls().size());
- assertTrue(mNotificationRecordLogger.get(0).shouldLog);
- assertFalse(mNotificationRecordLogger.get(1).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
+ assertEquals(
+ NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+ mNotificationRecordLogger.get(0).event);
+ assertFalse(mNotificationRecordLogger.get(1).shouldLogReported);
+ assertNull(mNotificationRecordLogger.get(1).event);
+ }
+
+ @Test
+ public void testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate() throws Exception {
+ final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate";
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
+ generateNotificationRecord(null).getNotification(),
+ 0);
+ final Notification notif = generateNotificationRecord(null).getNotification();
+ notif.extras.putString(Notification.EXTRA_TITLE, "Changed title");
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0);
+ waitForIdle();
+ assertEquals(2, mNotificationRecordLogger.getCalls().size());
+ assertEquals(
+ NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
+ mNotificationRecordLogger.get(0).event);
+ assertNull(mNotificationRecordLogger.get(1).event);
}
@Test
@@ -1224,20 +1243,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
mNotificationRecordLogger.get(0).event);
- assertTrue(mNotificationRecordLogger.get(0).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
- assertEquals(REASON_APP_CANCEL, mNotificationRecordLogger.get(1).reason);
assertEquals(
NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL,
mNotificationRecordLogger.get(1).event);
- assertTrue(mNotificationRecordLogger.get(1).shouldLog);
assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId());
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
mNotificationRecordLogger.get(2).event);
- assertTrue(mNotificationRecordLogger.get(2).shouldLog);
+ assertTrue(mNotificationRecordLogger.get(2).shouldLogReported);
// New instance ID because notification was canceled before re-post
assertEquals(1, mNotificationRecordLogger.get(2).getInstanceId());
}
@@ -3396,11 +3413,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// so we only get the cancel notification.
assertEquals(1, mNotificationRecordLogger.getCalls().size());
- assertEquals(REASON_CANCEL, mNotificationRecordLogger.get(0).reason);
assertEquals(
NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD,
mNotificationRecordLogger.get(0).event);
- assertTrue(mNotificationRecordLogger.get(0).shouldLog);
assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
}
@@ -4326,6 +4341,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testOnNotificationVisibilityChanged_triggersVisibilityLog() {
+ final NotificationRecord r = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ mService.mNotificationDelegate.onNotificationVisibilityChanged(new NotificationVisibility[]
+ {NotificationVisibility.obtain(r.getKey(), 1, 1, true)},
+ new NotificationVisibility[]{});
+
+ assertEquals(1, mNotificationRecordLogger.getCalls().size());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_OPEN,
+ mNotificationRecordLogger.get(0).event);
+ assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId());
+
+ mService.mNotificationDelegate.onNotificationVisibilityChanged(
+ new NotificationVisibility[]{},
+ new NotificationVisibility[]
+ {NotificationVisibility.obtain(r.getKey(), 1, 1, true)}
+ );
+
+ assertEquals(2, mNotificationRecordLogger.getCalls().size());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLOSE,
+ mNotificationRecordLogger.get(1).event);
+ assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId());
+ }
+
+ @Test
public void testSetNotificationsShownFromListener_triggersInterruptionUsageStat()
throws RemoteException {
final NotificationRecord r = generateNotificationRecord(
@@ -5373,21 +5416,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testCancelAllNotifications_cancelsBubble() throws Exception {
- final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
- nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
- mService.addNotification(nr);
-
- mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
- waitForIdle();
-
- StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
- assertEquals(0, notifs.length);
- assertEquals(0, mService.getNotificationRecordCount());
- }
-
- @Test
- public void testAppCancelNotifications_cancelsBubbles() throws Exception {
+ public void testCancelNotificationsFromApp_cancelsBubbles() throws Exception {
final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE;
@@ -5413,6 +5442,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testCancelAllNotificationsFromApp_cancelsBubble() throws Exception {
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+ mService.addNotification(nr);
+
+ mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
+ waitForIdle();
+
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(0, notifs.length);
+ assertEquals(0, mService.getNotificationRecordCount());
+ }
+
+ @Test
public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception {
final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
@@ -5448,6 +5491,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testCancelAllNotificationsFromStatusBar_ignoresBubble() throws Exception {
+ // GIVEN a notification bubble
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+ mService.addNotification(nr);
+
+ // WHEN the status bar clears all notifications
+ mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+ nr.getSbn().getUserId());
+ waitForIdle();
+
+ // THEN the bubble notification does not get removed
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifs.length);
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+
+ @Test
public void testGetAllowedAssistantAdjustments() throws Exception {
List<String> capabilities = mBinderService.getAllowedAssistantAdjustments(null);
assertNotNull(capabilities);
@@ -6105,6 +6167,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testNotificationBubbles_bubbleStays_whenClicked()
+ throws Exception {
+ // GIVEN a notification that has the auto cancels flag (cancel on click) and is a bubble
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.getSbn().getNotification().flags |= FLAG_BUBBLE | FLAG_AUTO_CANCEL;
+ mService.addNotification(nr);
+
+ // WHEN we click the notification
+ final NotificationVisibility nv = NotificationVisibility.obtain(nr.getKey(), 1, 2, true);
+ mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+ nr.getKey(), nv);
+ waitForIdle();
+
+ // THEN the bubble should still exist
+ StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsAfter.length);
+ }
+
+ @Test
public void testLoadDefaultApprovedServices_emptyResources() {
TestableResources tr = mContext.getOrCreateTestableResources();
tr.addOverride(com.android.internal.R.string.config_defaultListenerAccessPackages, "");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
index b120dbee03c5..2a17bae57c8e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
@@ -26,24 +26,26 @@ import java.util.List;
*/
class NotificationRecordLoggerFake implements NotificationRecordLogger {
static class CallRecord extends NotificationRecordPair {
- static final int INVALID = -1;
- public int position = INVALID, buzzBeepBlink = INVALID, reason = INVALID;
- public boolean shouldLog;
public UiEventLogger.UiEventEnum event;
+
+ // The following fields are only relevant to maybeLogNotificationPosted() calls.
+ static final int INVALID = -1;
+ public int position = INVALID, buzzBeepBlink = INVALID;
+ public boolean shouldLogReported;
+
CallRecord(NotificationRecord r, NotificationRecord old, int position,
int buzzBeepBlink) {
super(r, old);
-
this.position = position;
this.buzzBeepBlink = buzzBeepBlink;
- shouldLog = shouldLog(buzzBeepBlink);
- event = NotificationReportedEvent.fromRecordPair(this);
+ shouldLogReported = shouldLogReported(buzzBeepBlink);
+ event = shouldLogReported ? NotificationReportedEvent.fromRecordPair(this) : null;
}
- CallRecord(NotificationRecord r, int reason, int dismissalSurface) {
+
+ CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) {
super(r, null);
- this.reason = reason;
- shouldLog = true;
- event = NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface);
+ shouldLogReported = false;
+ this.event = event;
}
}
private List<CallRecord> mCalls = new ArrayList<>();
@@ -57,14 +59,19 @@ class NotificationRecordLoggerFake implements NotificationRecordLogger {
}
@Override
- public void logNotificationReported(NotificationRecord r, NotificationRecord old,
+ public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old,
int position, int buzzBeepBlink) {
mCalls.add(new CallRecord(r, old, position, buzzBeepBlink));
}
@Override
public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
- mCalls.add(new CallRecord(r, reason, dismissalSurface));
+ mCalls.add(new CallRecord(r,
+ NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface)));
}
+ @Override
+ public void logNotificationVisibility(NotificationRecord r, boolean visible) {
+ mCalls.add(new CallRecord(r, NotificationEvent.fromVisibility(visible)));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
new file mode 100644
index 000000000000..8ac1d24333be
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+
+import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
+import static com.android.server.wm.DisplayArea.Type.ANY;
+import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import static java.util.stream.Collectors.toList;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import org.hamcrest.CustomTypeSafeMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Presubmit
+public class DisplayAreaPolicyBuilderTest {
+
+ @Rule
+ public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+
+ private TestWindowManagerPolicy mPolicy = new TestWindowManagerPolicy(null, null);
+
+ @Test
+ public void testBuilder() {
+ WindowManagerService wms = mSystemServices.getWindowManagerService();
+ DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms);
+ DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime");
+ DisplayArea<ActivityStack> tasks = new DisplayArea<>(wms, ANY, "Tasks");
+
+ final Feature foo;
+ final Feature bar;
+
+ DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+ .addFeature(foo = new Feature.Builder(mPolicy, "Foo")
+ .upTo(TYPE_STATUS_BAR)
+ .and(TYPE_NAVIGATION_BAR)
+ .build())
+ .addFeature(bar = new Feature.Builder(mPolicy, "Bar")
+ .all()
+ .except(TYPE_STATUS_BAR)
+ .build())
+ .build(wms, mock(DisplayContent.class), root, ime, tasks);
+
+ policy.attachDisplayAreas();
+
+ assertThat(policy.getDisplayAreas(foo), is(not(empty())));
+ assertThat(policy.getDisplayAreas(bar), is(not(empty())));
+
+ assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)),
+ is(decendantOfOneOf(policy.getDisplayAreas(foo))));
+ assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)),
+ is(not(decendantOfOneOf(policy.getDisplayAreas(bar)))));
+
+ assertThat(tasks,
+ is(decendantOfOneOf(policy.getDisplayAreas(foo))));
+ assertThat(tasks,
+ is(decendantOfOneOf(policy.getDisplayAreas(bar))));
+
+ assertThat(ime,
+ is(decendantOfOneOf(policy.getDisplayAreas(foo))));
+ assertThat(ime,
+ is(decendantOfOneOf(policy.getDisplayAreas(bar))));
+
+ List<DisplayArea<?>> actualOrder = collectLeafAreas(root);
+ Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, tasks);
+ actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList());
+
+ Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets,
+ v -> v.stream().min(Integer::compareTo).get());
+ Map<DisplayArea<?>, Integer> expectedByMaxLayer = mapValues(zSets,
+ v -> v.stream().max(Integer::compareTo).get());
+
+ assertThat(expectedByMinLayer, is(equalTo(expectedByMaxLayer)));
+ assertThat(actualOrder, is(equalTo(expectedByMaxLayer)));
+ }
+
+ private <K, V, R> Map<K, R> mapValues(Map<K, V> zSets, Function<V, R> f) {
+ return zSets.entrySet().stream().collect(Collectors.toMap(
+ Map.Entry::getKey,
+ e -> f.apply(e.getValue())));
+ }
+
+ private List<DisplayArea<?>> collectLeafAreas(DisplayArea<?> root) {
+ ArrayList<DisplayArea<?>> leafs = new ArrayList<>();
+ traverseLeafAreas(root, leafs::add);
+ return leafs;
+ }
+
+ private Map<DisplayArea<?>, Set<Integer>> calculateZSets(
+ DisplayAreaPolicyBuilder.Result policy, DisplayArea.Root root,
+ DisplayArea<WindowContainer> ime,
+ DisplayArea<ActivityStack> tasks) {
+ Map<DisplayArea<?>, Set<Integer>> zSets = new HashMap<>();
+ int[] types = {TYPE_STATUS_BAR, TYPE_NAVIGATION_BAR, TYPE_PRESENTATION,
+ TYPE_APPLICATION_OVERLAY};
+ for (int type : types) {
+ WindowToken token = tokenOfType(type);
+ recordLayer(policy.findAreaForToken(token), token.getWindowLayerFromType(), zSets);
+ }
+ recordLayer(tasks, APPLICATION_LAYER, zSets);
+ recordLayer(ime, mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD), zSets);
+ return zSets;
+ }
+
+ private void recordLayer(DisplayArea<?> area, int layer,
+ Map<DisplayArea<?>, Set<Integer>> zSets) {
+ zSets.computeIfAbsent(area, k -> new HashSet<>()).add(layer);
+ }
+
+ private Matcher<WindowContainer> decendantOfOneOf(List<? extends WindowContainer> expected) {
+ return new CustomTypeSafeMatcher<WindowContainer>("descendant of one of " + expected) {
+ @Override
+ protected boolean matchesSafely(WindowContainer actual) {
+ for (WindowContainer expected : expected) {
+ WindowContainer candidate = actual;
+ while (candidate != null && candidate.getParent() != candidate) {
+ if (candidate.getParent() == expected) {
+ return true;
+ }
+ candidate = candidate.getParent();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void describeMismatchSafely(WindowContainer item,
+ Description description) {
+ description.appendText("was ").appendValue(item);
+ while (item != null && item.getParent() != item) {
+ item = item.getParent();
+ description.appendText(", child of ").appendValue(item);
+ }
+ }
+ };
+ }
+
+ private WindowToken tokenOfType(int type) {
+ WindowToken m = mock(WindowToken.class);
+ when(m.getWindowLayerFromType()).thenReturn(mPolicy.getWindowLayerFromTypeLw(type));
+ return m;
+ }
+
+ private static void traverseLeafAreas(DisplayArea<?> root, Consumer<DisplayArea<?>> consumer) {
+ boolean leaf = true;
+ for (int i = 0; i < root.getChildCount(); i++) {
+ WindowContainer child = root.getChildAt(i);
+ if (child instanceof DisplayArea<?>) {
+ traverseLeafAreas((DisplayArea<?>) child, consumer);
+ leaf = false;
+ }
+ }
+ if (leaf) {
+ consumer.accept(root);
+ }
+ }
+
+ private static class SurfacelessDisplayAreaRoot extends DisplayArea.Root {
+
+ SurfacelessDisplayAreaRoot(WindowManagerService wms) {
+ super(wms);
+ }
+
+ @Override
+ SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+ return new MockSurfaceControlBuilder();
+ }
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
index c1a1d5ecd3c8..31206315618e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
@@ -34,13 +34,13 @@ public class DisplayAreaProviderTest {
@Test
public void testFromResources_emptyProvider() {
Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")),
- Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+ Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class));
}
@Test
public void testFromResources_nullProvider() {
Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)),
- Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+ Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index f517881d835b..8ad75053060f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -56,6 +56,7 @@ import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.StrictMode;
import android.os.UserHandle;
+import android.util.Log;
import android.view.InputChannel;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -120,11 +121,22 @@ public class SystemServicesTestRule implements TestRule {
return new Statement() {
@Override
public void evaluate() throws Throwable {
+ Throwable throwable = null;
try {
runWithDexmakerShareClassLoader(SystemServicesTestRule.this::setUp);
base.evaluate();
+ } catch (Throwable t) {
+ throwable = t;
} finally {
- tearDown();
+ try {
+ tearDown();
+ } catch (Throwable t) {
+ if (throwable != null) {
+ Log.e("SystemServicesTestRule", "Suppressed: ", throwable);
+ t.addSuppressed(throwable);
+ }
+ throw t;
+ }
}
}
};
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 0d5e6885a2fd..52213d8c4fae 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -17,6 +17,7 @@
package android.telecom;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -458,8 +459,14 @@ public final class Call {
/** Call supports the deflect feature. */
public static final int CAPABILITY_SUPPORT_DEFLECT = 0x01000000;
+ /**
+ * Call supports adding participants to the call via
+ * {@link #addConferenceParticipants(List)}.
+ * @hide
+ */
+ public static final int CAPABILITY_ADD_PARTICIPANT = 0x02000000;
//******************************************************************************************
- // Next CAPABILITY value: 0x02000000
+ // Next CAPABILITY value: 0x04000000
//******************************************************************************************
/**
@@ -689,6 +696,9 @@ public final class Call {
if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
builder.append(" CAPABILITY_SUPPORT_DEFLECT");
}
+ if (can(capabilities, CAPABILITY_ADD_PARTICIPANT)) {
+ builder.append(" CAPABILITY_ADD_PARTICIPANT");
+ }
builder.append("]");
return builder.toString();
}
@@ -1703,6 +1713,17 @@ public final class Call {
}
/**
+ * Pulls participants to existing call by forming a conference call.
+ * See {@link Details#CAPABILITY_ADD_PARTICIPANT}.
+ *
+ * @param participants participants to be pulled to existing call.
+ * @hide
+ */
+ public void addConferenceParticipants(@NonNull List<Uri> participants) {
+ mInCallAdapter.addConferenceParticipants(mTelecomCallId, participants);
+ }
+
+ /**
* Initiates a request to the {@link ConnectionService} to pull an external call to the local
* device.
* <p>
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 9c80c79bcebc..f019a9d33005 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -324,6 +324,13 @@ public abstract class Conference extends Conferenceable {
public void onConnectionAdded(Connection connection) {}
/**
+ * Notifies the {@link Conference} of a request to add a new participants to the conference call
+ * @param participants that will be added to this conference call
+ * @hide
+ */
+ public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+ /**
* Notifies this Conference, which is in {@code STATE_RINGING}, of
* a request to accept.
* For managed {@link ConnectionService}s, this will be called when the user answers a call via
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 5f860a1530bb..3b0ba2548660 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -381,8 +381,14 @@ public abstract class Connection extends Conferenceable {
/** Call supports the deflect feature. */
public static final int CAPABILITY_SUPPORT_DEFLECT = 0x02000000;
+ /**
+ * When set, indicates that this {@link Connection} supports initiation of a conference call
+ * by directly adding participants using {@link #onAddConferenceParticipants(List)}.
+ * @hide
+ */
+ public static final int CAPABILITY_ADD_PARTICIPANT = 0x04000000;
//**********************************************************************************************
- // Next CAPABILITY value: 0x04000000
+ // Next CAPABILITY value: 0x08000000
//**********************************************************************************************
/**
@@ -958,7 +964,9 @@ public abstract class Connection extends Conferenceable {
if ((capabilities & CAPABILITY_SUPPORT_DEFLECT) == CAPABILITY_SUPPORT_DEFLECT) {
builder.append(isLong ? " CAPABILITY_SUPPORT_DEFLECT" : " sup_def");
}
-
+ if ((capabilities & CAPABILITY_ADD_PARTICIPANT) == CAPABILITY_ADD_PARTICIPANT) {
+ builder.append(isLong ? " CAPABILITY_ADD_PARTICIPANT" : " add_participant");
+ }
builder.append("]");
return builder.toString();
}
@@ -2975,6 +2983,14 @@ public abstract class Connection extends Conferenceable {
public void onSeparate() {}
/**
+ * Supports initiation of a conference call by directly adding participants to an ongoing call.
+ *
+ * @param participants with which conference call will be formed.
+ * @hide
+ */
+ public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+ /**
* Notifies this Connection of a request to abort.
*/
public void onAbort() {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index f77e52964e08..2aea723cf418 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -141,6 +141,7 @@ public abstract class ConnectionService extends Service {
private static final String SESSION_SPLIT_CONFERENCE = "CS.sFC";
private static final String SESSION_MERGE_CONFERENCE = "CS.mC";
private static final String SESSION_SWAP_CONFERENCE = "CS.sC";
+ private static final String SESSION_ADD_PARTICIPANT = "CS.aP";
private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
@@ -194,6 +195,7 @@ public abstract class ConnectionService extends Service {
private static final int MSG_CREATE_CONFERENCE_COMPLETE = 36;
private static final int MSG_CREATE_CONFERENCE_FAILED = 37;
private static final int MSG_REJECT_WITH_REASON = 38;
+ private static final int MSG_ADD_PARTICIPANT = 39;
private static Connection sNullConnection;
@@ -626,6 +628,21 @@ public abstract class ConnectionService extends Service {
}
@Override
+ public void addConferenceParticipants(String callId, List<Uri> participants,
+ Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_ADD_PARTICIPANT);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = participants;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ADD_PARTICIPANT, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void onPostDialContinue(String callId, boolean proceed, Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_POST_DIAL_CONT);
try {
@@ -1223,6 +1240,19 @@ public abstract class ConnectionService extends Service {
}
break;
}
+ case MSG_ADD_PARTICIPANT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_ADD_PARTICIPANT);
+ addConferenceParticipants((String) args.arg1, (List<Uri>)args.arg2);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
+
case MSG_ON_POST_DIAL_CONTINUE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -2151,6 +2181,17 @@ public abstract class ConnectionService extends Service {
}
}
+ private void addConferenceParticipants(String callId, List<Uri> participants) {
+ Log.d(this, "addConferenceParticipants(%s)", callId);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "addConferenceParticipants")
+ .onAddConferenceParticipants(participants);
+ } else {
+ findConferenceForAction(callId, "addConferenceParticipants")
+ .onAddConferenceParticipants(participants);
+ }
+ }
+
/**
* Notifies a {@link Connection} of a request to pull an external call.
*
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 594c1eb392b3..9d29174059ad 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -283,6 +283,20 @@ public final class InCallAdapter {
}
/**
+ * Instructs Telecom to pull participants to existing call
+ *
+ * @param callId The unique ID of the call.
+ * @param participants participants to be pulled to existing call.
+ */
+ public void addConferenceParticipants(String callId, List<Uri> participants) {
+ try {
+ mAdapter.addConferenceParticipants(callId, participants);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+
+ /**
* Instructs Telecom to split the specified call from any conference call with which it may be
* connected.
*
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index ebfa3a15639a..982e5f30e28c 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
+import android.app.UiModeManager;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
@@ -43,12 +44,32 @@ import java.util.List;
* phone calls.
* <h2>Becoming the Default Phone App</h2>
* The default dialer/phone app is one which provides the in-call user interface while the device is
- * in a call. A device is bundled with a system provided default dialer/phone app. The user may
- * choose a single app to take over this role from the system app. An app which wishes to fulfill
- * one this role uses the {@code android.app.role.RoleManager} to request that they fill the role.
+ * in a call. It also provides the user with a means to initiate calls and see a history of calls
+ * on their device. A device is bundled with a system provided default dialer/phone app. The user
+ * may choose a single app to take over this role from the system app. An app which wishes to
+ * fulfill one this role uses the {@link android.app.role.RoleManager} to request that they fill the
+ * {@link android.app.role.RoleManager#ROLE_DIALER} role.
* <p>
- * An app filling the role of the default phone app provides a user interface while the device is in
- * a call, and the device is not in car mode.
+ * The default phone app provides a user interface while the device is in a call, and the device is
+ * not in car mode (i.e. {@link UiModeManager#getCurrentModeType()} is not
+ * {@link android.content.res.Configuration#UI_MODE_TYPE_CAR}).
+ * <p>
+ * In order to fill the {@link android.app.role.RoleManager#ROLE_DIALER} role, an app must meet a
+ * number of requirements:
+ * <ul>
+ * <li>It must handle the {@link Intent#ACTION_DIAL} intent. This means the app must provide
+ * a dial pad UI for the user to initiate outgoing calls.</li>
+ * <li>It must fully implement the {@link InCallService} API and provide both an incoming call
+ * UI, as well as an ongoing call UI.</li>
+ * </ul>
+ * <p>
+ * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} crashes during
+ * {@link InCallService} binding, the Telecom framework will automatically fall back to using the
+ * dialer app pre-loaded on the device. The system will display a notification to the user to let
+ * them know that the app has crashed and that their call was continued using the pre-loaded dialer
+ * app.
+ * <p>
+ * Further, the pre-loaded dialer will ALWAYS be used when the user places an emergency call.
* <p>
* Below is an example manifest registration for an {@code InCallService}. The meta-data
* {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular
@@ -82,6 +103,11 @@ import java.util.List;
* <action android:name="android.intent.action.DIAL" />
* <category android:name="android.intent.category.DEFAULT" />
* </intent-filter>
+ * <intent-filter>
+ * <action android:name="android.intent.action.DIAL" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:scheme="tel" />
+ * </intent-filter>
* </activity>
* }
* </pre>
@@ -111,6 +137,7 @@ import java.util.List;
* }
* }
* }
+ * }
* </pre>
* <p id="incomingCallNotification">
* <h3>Showing the Incoming Call Notification</h3>
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 4249dff151c7..a397d77db2f6 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -104,6 +104,9 @@ oneway interface IConnectionService {
void swapConference(String conferenceCallId, in Session.Info sessionInfo);
+ void addConferenceParticipants(String CallId, in List<Uri> participants,
+ in Session.Info sessionInfo);
+
void onPostDialContinue(String callId, boolean proceed, in Session.Info sessionInfo);
void pullExternalCall(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index eb2d714fe3f4..9beff22ce52e 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -67,6 +67,8 @@ oneway interface IInCallAdapter {
void swapConference(String callId);
+ void addConferenceParticipants(String callId, in List<Uri> participants);
+
void turnOnProximitySensor();
void turnOffProximitySensor(boolean screenOnImmediately);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ebb53c50ca98..51b4a31ea8b2 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1090,6 +1090,14 @@ public class CarrierConfigManager {
"support_adhoc_conference_calls_bool";
/**
+ * Determines whether conference participants can be added to existing call. When {@code true},
+ * adding conference participants to existing call is supported, {@code false otherwise}.
+ * @hide
+ */
+ public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL =
+ "support_add_conference_participants_bool";
+
+ /**
* Determines whether conference calls are supported by a carrier. When {@code true},
* conference calling is supported, {@code false otherwise}.
*/
@@ -4004,6 +4012,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL, false);
+ sDefaults.putBoolean(KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 54980a29c0a6..250d9e8b212e 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -256,337 +256,6 @@ public final class PreciseDisconnectCause {
/** Access Blocked by CDMA network. */
public static final int CDMA_ACCESS_BLOCKED = 1009;
- /** Mapped from ImsReasonInfo */
- // TODO: remove ImsReasonInfo from preciseDisconnectCause
- /* The passed argument is an invalid */
- /** @hide */
- public static final int LOCAL_ILLEGAL_ARGUMENT = 1200;
- // The operation is invoked in invalid call state
- /** @hide */
- public static final int LOCAL_ILLEGAL_STATE = 1201;
- // IMS service internal error
- /** @hide */
- public static final int LOCAL_INTERNAL_ERROR = 1202;
- // IMS service goes down (service connection is lost)
- /** @hide */
- public static final int LOCAL_IMS_SERVICE_DOWN = 1203;
- // No pending incoming call exists
- /** @hide */
- public static final int LOCAL_NO_PENDING_CALL = 1204;
- // Service unavailable; by power off
- /** @hide */
- public static final int LOCAL_POWER_OFF = 1205;
- // Service unavailable; by low battery
- /** @hide */
- public static final int LOCAL_LOW_BATTERY = 1206;
- // Service unavailable; by out of service (data service state)
- /** @hide */
- public static final int LOCAL_NETWORK_NO_SERVICE = 1207;
- /* Service unavailable; by no LTE coverage
- * (VoLTE is not supported even though IMS is registered)
- */
- /** @hide */
- public static final int LOCAL_NETWORK_NO_LTE_COVERAGE = 1208;
- /** Service unavailable; by located in roaming area */
- /** @hide */
- public static final int LOCAL_NETWORK_ROAMING = 1209;
- /** Service unavailable; by IP changed */
- /** @hide */
- public static final int LOCAL_NETWORK_IP_CHANGED = 1210;
- /** Service unavailable; other */
- /** @hide */
- public static final int LOCAL_SERVICE_UNAVAILABLE = 1211;
- /* Service unavailable; IMS connection is lost (IMS is not registered) */
- /** @hide */
- public static final int LOCAL_NOT_REGISTERED = 1212;
- /** Max call exceeded */
- /** @hide */
- public static final int LOCAL_MAX_CALL_EXCEEDED = 1213;
- /** Call decline */
- /** @hide */
- public static final int LOCAL_CALL_DECLINE = 1214;
- /** SRVCC is in progress */
- /** @hide */
- public static final int LOCAL_CALL_VCC_ON_PROGRESSING = 1215;
- /** Resource reservation is failed (QoS precondition) */
- /** @hide */
- public static final int LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 1216;
- /** Retry CS call; VoLTE service can't be provided by the network or remote end
- * Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
- * @hide
- */
- public static final int LOCAL_CALL_CS_RETRY_REQUIRED = 1217;
- /** Retry VoLTE call; VoLTE service can't be provided by the network temporarily */
- /** @hide */
- public static final int LOCAL_CALL_VOLTE_RETRY_REQUIRED = 1218;
- /** IMS call is already terminated (in TERMINATED state) */
- /** @hide */
- public static final int LOCAL_CALL_TERMINATED = 1219;
- /** Handover not feasible */
- /** @hide */
- public static final int LOCAL_HO_NOT_FEASIBLE = 1220;
-
- /** 1xx waiting timer is expired after sending INVITE request (MO only) */
- /** @hide */
- public static final int TIMEOUT_1XX_WAITING = 1221;
- /** User no answer during call setup operation (MO/MT)
- * MO : 200 OK to INVITE request is not received,
- * MT : No action from user after alerting the call
- * @hide
- */
- public static final int TIMEOUT_NO_ANSWER = 1222;
- /** User no answer during call update operation (MO/MT)
- * MO : 200 OK to re-INVITE request is not received,
- * MT : No action from user after alerting the call
- * @hide
- */
- public static final int TIMEOUT_NO_ANSWER_CALL_UPDATE = 1223;
-
- /**
- * STATUSCODE (SIP response code) (IMS -> Telephony)
- */
- /** SIP request is redirected */
- /** @hide */
- public static final int SIP_REDIRECTED = 1300;
- /** 4xx responses */
- /** 400 : Bad Request */
- /** @hide */
- public static final int SIP_BAD_REQUEST = 1310;
- /** 403 : Forbidden */
- /** @hide */
- public static final int SIP_FORBIDDEN = 1311;
- /** 404 : Not Found */
- /** @hide */
- public static final int SIP_NOT_FOUND = 1312;
- /** 415 : Unsupported Media Type
- * 416 : Unsupported URI Scheme
- * 420 : Bad Extension
- */
- /** @hide */
- public static final int SIP_NOT_SUPPORTED = 1313;
- /** 408 : Request Timeout */
- /** @hide */
- public static final int SIP_REQUEST_TIMEOUT = 1314;
- /** 480 : Temporarily Unavailable */
- /** @hide */
- public static final int SIP_TEMPRARILY_UNAVAILABLE = 1315;
- /** 484 : Address Incomplete */
- /** @hide */
- public static final int SIP_BAD_ADDRESS = 1316;
- /** 486 : Busy Here
- * 600 : Busy Everywhere
- */
- /** @hide */
- public static final int SIP_BUSY = 1317;
- /** 487 : Request Terminated */
- /** @hide */
- public static final int SIP_REQUEST_CANCELLED = 1318;
- /** 406 : Not Acceptable
- * 488 : Not Acceptable Here
- * 606 : Not Acceptable
- */
- /** @hide */
- public static final int SIP_NOT_ACCEPTABLE = 1319;
- /** 410 : Gone
- * 604 : Does Not Exist Anywhere
- */
- /** @hide */
- public static final int SIP_NOT_REACHABLE = 1320;
- /** Others */
- /** @hide */
- public static final int SIP_CLIENT_ERROR = 1321;
- /** 481 : Transaction Does Not Exist */
- /** @hide */
- public static final int SIP_TRANSACTION_DOES_NOT_EXIST = 1322;
- /** 5xx responses
- * 501 : Server Internal Error
- */
- /** @hide */
- public static final int SIP_SERVER_INTERNAL_ERROR = 1330;
- /** 503 : Service Unavailable */
- /** @hide */
- public static final int SIP_SERVICE_UNAVAILABLE = 1331;
- /** 504 : Server Time-out */
- /** @hide */
- public static final int SIP_SERVER_TIMEOUT = 1332;
- /** Others */
- /** @hide */
- public static final int SIP_SERVER_ERROR = 1333;
- /** 6xx responses
- * 603 : Decline
- */
- /** @hide */
- public static final int SIP_USER_REJECTED = 1340;
- /** Others */
- /** @hide */
- public static final int SIP_GLOBAL_ERROR = 1341;
- /** Emergency failure */
- /** @hide */
- public static final int EMERGENCY_TEMP_FAILURE = 1342;
- /** @hide */
- public static final int EMERGENCY_PERM_FAILURE = 1343;
- /** Media resource initialization failed */
- /** @hide */
- public static final int MEDIA_INIT_FAILED = 1400;
- /** RTP timeout (no audio / video traffic in the session) */
- /** @hide */
- public static final int MEDIA_NO_DATA = 1401;
- /** Media is not supported; so dropped the call */
- /** @hide */
- public static final int MEDIA_NOT_ACCEPTABLE = 1402;
- /** Unknown media related errors */
- /** @hide */
- public static final int MEDIA_UNSPECIFIED = 1403;
- /** User triggers the call end */
- /** @hide */
- public static final int USER_TERMINATED = 1500;
- /** No action while an incoming call is ringing */
- /** @hide */
- public static final int USER_NOANSWER = 1501;
- /** User ignores an incoming call */
- /** @hide */
- public static final int USER_IGNORE = 1502;
- /** User declines an incoming call */
- /** @hide */
- public static final int USER_DECLINE = 1503;
- /** Device declines/ends a call due to low battery */
- /** @hide */
- public static final int LOW_BATTERY = 1504;
- /** Device declines call due to blacklisted call ID */
- /** @hide */
- public static final int BLACKLISTED_CALL_ID = 1505;
- /** The call is terminated by the network or remote user */
- /** @hide */
- public static final int USER_TERMINATED_BY_REMOTE = 1510;
-
- /**
- * UT
- */
- /** @hide */
- public static final int UT_NOT_SUPPORTED = 1800;
- /** @hide */
- public static final int UT_SERVICE_UNAVAILABLE = 1801;
- /** @hide */
- public static final int UT_OPERATION_NOT_ALLOWED = 1802;
- /** @hide */
- public static final int UT_NETWORK_ERROR = 1803;
- /** @hide */
- public static final int UT_CB_PASSWORD_MISMATCH = 1804;
-
- /**
- * ECBM
- * @hide
- */
- public static final int ECBM_NOT_SUPPORTED = 1900;
-
- /**
- * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework.
- * @hide
- */
- public static final int MULTIENDPOINT_NOT_SUPPORTED = 1901;
-
- /**
- * CALL DROP error codes (Call could drop because of many reasons like Network not available,
- * handover, failed, etc)
- */
-
- /**
- * CALL DROP error code for the case when a device is ePDG capable and when the user is on an
- * active wifi call and at the edge of coverage and there is no qualified LTE network available
- * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error
- * code is received as part of the handover message.
- * @hide
- */
- public static final int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 2000;
-
- /**
- * MT call has ended due to a release from the network
- * because the call was answered elsewhere
- * @hide
- */
- public static final int ANSWERED_ELSEWHERE = 2100;
-
- /**
- * For MultiEndpoint - Call Pull request has failed
- * @hide
- */
- public static final int CALL_PULL_OUT_OF_SYNC = 2101;
-
- /**
- * For MultiEndpoint - Call has been pulled from primary to secondary
- * @hide
- */
- public static final int CALL_PULLED = 2102;
-
- /**
- * Supplementary services (HOLD/RESUME) failure error codes.
- * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision.
- * @hide
- */
- public static final int SUPP_SVC_FAILED = 2300;
- /** @hide */
- public static final int SUPP_SVC_CANCELLED = 2301;
- /** @hide */
- public static final int SUPP_SVC_REINVITE_COLLISION = 2302;
-
- /**
- * DPD Procedure received no response or send failed
- * @hide
- */
- public static final int IWLAN_DPD_FAILURE = 2400;
-
- /**
- * Establishment of the ePDG Tunnel Failed
- * @hide
- */
- public static final int EPDG_TUNNEL_ESTABLISH_FAILURE = 2500;
-
- /**
- * Re-keying of the ePDG Tunnel Failed; may not always result in teardown
- * @hide
- */
- public static final int EPDG_TUNNEL_REKEY_FAILURE = 2501;
-
- /**
- * Connection to the packet gateway is lost
- * @hide
- */
- public static final int EPDG_TUNNEL_LOST_CONNECTION = 2502;
-
- /**
- * The maximum number of calls allowed has been reached. Used in a multi-endpoint scenario
- * where the number of calls across all connected devices has reached the maximum.
- * @hide
- */
- public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED = 2503;
-
- /**
- * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has
- * declined the call. Used in a multi-endpoint scenario where a remote device declined an
- * incoming call.
- * @hide
- */
- public static final int REMOTE_CALL_DECLINE = 2504;
-
- /**
- * Indicates the call was disconnected due to the user reaching their data limit.
- * @hide
- */
- public static final int DATA_LIMIT_REACHED = 2505;
-
- /**
- * Indicates the call was disconnected due to the user disabling cellular data.
- * @hide
- */
- public static final int DATA_DISABLED = 2506;
-
- /**
- * Indicates a call was disconnected due to loss of wifi signal.
- * @hide
- */
- public static final int WIFI_LOST = 2507;
-
-
/* OEM specific error codes. To be used by OEMs when they don't want to
reveal error code which would be replaced by ERROR_UNSPECIFIED */
public static final int OEM_CAUSE_1 = 0xf001;
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 20d0e9690e8e..ae20cae36839 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -430,10 +430,9 @@ public class ApkVerityTest extends BaseHostJUnit4Test {
private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
String apkPath = getApkPath(TARGET_PACKAGE);
String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
+ // Exclude directories since we only care about files.
HashSet<String> actualFiles = new HashSet<>(Arrays.asList(
- expectRemoteCommandToSucceed("ls " + appDir).split("\n")));
- assertTrue(actualFiles.remove("lib"));
- assertTrue(actualFiles.remove("oat"));
+ expectRemoteCommandToSucceed("ls -p " + appDir + " | grep -v '/'").split("\n")));
HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames));
assertEquals(expectedFiles, actualFiles);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 155c61f3f8c7..eb78529e8715 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -148,6 +148,7 @@ public class VpnTest {
@Mock private AppOpsManager mAppOps;
@Mock private NotificationManager mNotificationManager;
@Mock private Vpn.SystemServices mSystemServices;
+ @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
@Mock private ConnectivityManager mConnectivityManager;
@Mock private KeyStore mKeyStore;
private final VpnProfile mVpnProfile = new VpnProfile("key");
@@ -867,7 +868,8 @@ public class VpnTest {
* Mock some methods of vpn object.
*/
private Vpn createVpn(@UserIdInt int userId) {
- return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices);
+ return new Vpn(Looper.myLooper(), mContext, mNetService,
+ userId, mSystemServices, mIkev2SessionCreator);
}
private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 99901808ec3e..9c01d3643c19 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -738,7 +738,16 @@ public final class Credential implements Parcelable {
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append("IMSI: ").append(mImsi).append("\n");
+ String imsi;
+ if (mImsi != null) {
+ if (mImsi.length() > 6 && mImsi.charAt(6) != '*') {
+ // Truncate the full IMSI from the log
+ imsi = mImsi.substring(0, 6) + "****";
+ } else {
+ imsi = mImsi;
+ }
+ builder.append("IMSI: ").append(imsi).append("\n");
+ }
builder.append("EAPType: ").append(mEapType).append("\n");
return builder.toString();
}