Merge "MediaSessionManager: Add listener for Session2Token changes"
diff --git a/api/current.txt b/api/current.txt
index 3fc532b..584aedf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26099,6 +26099,25 @@
method public android.os.Bundle getResultData();
}
+ public final class Session2CommandGroup implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.util.Set<android.media.Session2Command> getCommands();
+ method public boolean hasCommand(android.media.Session2Command);
+ method public boolean hasCommand(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.Session2CommandGroup> CREATOR;
+ }
+
+ public static final class Session2CommandGroup.Builder {
+ ctor public Session2CommandGroup.Builder();
+ ctor public Session2CommandGroup.Builder(android.media.Session2CommandGroup);
+ method public android.media.Session2CommandGroup.Builder addCommand(android.media.Session2Command);
+ method public android.media.Session2CommandGroup.Builder addCommand(int);
+ method public android.media.Session2CommandGroup build();
+ method public android.media.Session2CommandGroup.Builder removeCommand(android.media.Session2Command);
+ method public android.media.Session2CommandGroup.Builder removeCommand(int);
+ }
+
public class SoundPool {
ctor public deprecated SoundPool(int, int, int);
method public final void autoPause();
@@ -30071,7 +30090,8 @@
method public android.net.wifi.p2p.WifiP2pConfig build();
method public android.net.wifi.p2p.WifiP2pConfig.Builder enablePersistentMode(boolean);
method public android.net.wifi.p2p.WifiP2pConfig.Builder setDeviceAddress(android.net.MacAddress);
- method public android.net.wifi.p2p.WifiP2pConfig.Builder setGroupOwnerBand(int);
+ method public android.net.wifi.p2p.WifiP2pConfig.Builder setGroupOperatingBand(int);
+ method public android.net.wifi.p2p.WifiP2pConfig.Builder setGroupOperatingFrequency(int);
method public android.net.wifi.p2p.WifiP2pConfig.Builder setNetworkName(java.lang.String);
method public android.net.wifi.p2p.WifiP2pConfig.Builder setPassphrase(java.lang.String);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index a4b3978..2935c4f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5662,7 +5662,7 @@
ctor public NotificationAssistantService();
method public final void adjustNotification(android.service.notification.Adjustment);
method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void onActionClicked(java.lang.String, android.app.Notification.Action, int);
+ method public void onActionInvoked(java.lang.String, android.app.Notification.Action, int);
method public final android.os.IBinder onBind(android.content.Intent);
method public void onNotificationDirectReplied(java.lang.String);
method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
diff --git a/api/test-current.txt b/api/test-current.txt
index 575875d..89ab802 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -219,6 +219,11 @@
method public abstract void onOpActiveChanged(int, int, java.lang.String, boolean);
}
+ public final class NotificationChannel implements android.os.Parcelable {
+ method public boolean isImportanceLockedByOEM();
+ method public void setImportanceLockedByOEM(boolean);
+ }
+
public final class NotificationChannelGroup implements android.os.Parcelable {
method public int getUserLockedFields();
method public void lockFields(int);
@@ -1275,7 +1280,7 @@
ctor public NotificationAssistantService();
method public final void adjustNotification(android.service.notification.Adjustment);
method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void onActionClicked(java.lang.String, android.app.Notification.Action, int);
+ method public void onActionInvoked(java.lang.String, android.app.Notification.Action, int);
method public final android.os.IBinder onBind(android.content.Intent);
method public void onNotificationDirectReplied(java.lang.String);
method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f347867..786d8d1 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -25,7 +25,9 @@
import "frameworks/base/core/proto/android/app/settings_enums.proto";
import "frameworks/base/core/proto/android/app/job/enums.proto";
import "frameworks/base/core/proto/android/bluetooth/enums.proto";
+import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
+import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
import "frameworks/base/core/proto/android/server/enums.proto";
import "frameworks/base/core/proto/android/server/location/enums.proto";
import "frameworks/base/core/proto/android/service/procstats_enum.proto";
@@ -177,6 +179,8 @@
DocsUIPickResultReported docs_ui_pick_result_reported = 118;
DocsUISearchModeReported docs_ui_search_mode_reported = 119;
DocsUISearchTypeReported docs_ui_search_type_reported = 120;
+ DataStallEvent data_stall_event = 121;
+ RescuePartyResetReported rescue_party_reset_reported = 122;
}
// Pulled events will start at field 10000.
@@ -3821,3 +3825,37 @@
// The latency period(in microseconds) it took for this DNS lookup to complete.
optional int32 latency_micros = 3;
}
+
+/**
+ * Logs when a data stall event occurs.
+ *
+ * Log from:
+ * frameworks/base/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+ */
+message DataStallEvent {
+ // Data stall evaluation type.
+ // See frameworks/base/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+ // Refer to the definition of DATA_STALL_EVALUATION_TYPE_*.
+ optional int32 evaluation_type = 1;
+ // See definition in data_stall_event.proto.
+ optional com.android.server.connectivity.ProbeResult validation_result = 2;
+ // See definition in data_stall_event.proto.
+ optional android.net.NetworkCapabilitiesProto.Transport network_type = 3;
+ // See definition in data_stall_event.proto.
+ optional com.android.server.connectivity.WifiData wifi_info = 4 [(log_mode) = MODE_BYTES];
+ // See definition in data_stall_event.proto.
+ optional com.android.server.connectivity.CellularData cell_info = 5 [(log_mode) = MODE_BYTES];
+ // See definition in data_stall_event.proto.
+ optional com.android.server.connectivity.DnsEvent dns_event = 6 [(log_mode) = MODE_BYTES];
+}
+
+/*
+ * Logs when RescueParty resets some set of experiment flags.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/RescueParty.java
+ */
+message RescuePartyResetReported {
+ // The rescue level of this reset. A value of 0 indicates missing or unknown level information.
+ optional int32 rescue_level = 1;
+}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index f2ad268..35098a0 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1090,7 +1090,7 @@
* called) or the service has been disconnected, this method will have
* no effect and return {@code false}.
*
- * @param scale the magnification scale to set, must be >= 1 and <= 5
+ * @param scale the magnification scale to set, must be >= 1 and <= 8
* @param animate {@code true} to animate from the current scale or
* {@code false} to set the scale immediately
* @return {@code true} on success, {@code false} on failure
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 41ceaaf..950e9aa 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.NotificationManager.Importance;
import android.content.ContentResolver;
@@ -168,6 +169,7 @@
// If this is a blockable system notification channel.
private boolean mBlockableSystem = false;
private boolean mAllowAppOverlay = DEFAULT_ALLOW_APP_OVERLAY;
+ private boolean mImportanceLockedByOEM;
/**
* Creates a notification channel.
@@ -230,6 +232,7 @@
mLightColor = in.readInt();
mBlockableSystem = in.readBoolean();
mAllowAppOverlay = in.readBoolean();
+ mImportanceLockedByOEM = in.readBoolean();
}
@Override
@@ -283,6 +286,7 @@
dest.writeInt(mLightColor);
dest.writeBoolean(mBlockableSystem);
dest.writeBoolean(mAllowAppOverlay);
+ dest.writeBoolean(mImportanceLockedByOEM);
}
/**
@@ -649,6 +653,22 @@
}
/**
+ * @hide
+ */
+ @TestApi
+ public void setImportanceLockedByOEM(boolean locked) {
+ mImportanceLockedByOEM = locked;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public boolean isImportanceLockedByOEM() {
+ return mImportanceLockedByOEM;
+ }
+
+ /**
* Returns whether the user has chosen the importance of this channel, either to affirm the
* initial selection from the app, or changed it to be higher or lower.
* @see #getImportance()
@@ -952,25 +972,26 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NotificationChannel that = (NotificationChannel) o;
- return getImportance() == that.getImportance() &&
- mBypassDnd == that.mBypassDnd &&
- getLockscreenVisibility() == that.getLockscreenVisibility() &&
- mLights == that.mLights &&
- getLightColor() == that.getLightColor() &&
- getUserLockedFields() == that.getUserLockedFields() &&
- isFgServiceShown() == that.isFgServiceShown() &&
- mVibrationEnabled == that.mVibrationEnabled &&
- mShowBadge == that.mShowBadge &&
- isDeleted() == that.isDeleted() &&
- isBlockableSystem() == that.isBlockableSystem() &&
- mAllowAppOverlay == that.mAllowAppOverlay &&
- Objects.equals(getId(), that.getId()) &&
- Objects.equals(getName(), that.getName()) &&
- Objects.equals(mDesc, that.mDesc) &&
- Objects.equals(getSound(), that.getSound()) &&
- Arrays.equals(mVibration, that.mVibration) &&
- Objects.equals(getGroup(), that.getGroup()) &&
- Objects.equals(getAudioAttributes(), that.getAudioAttributes());
+ return getImportance() == that.getImportance()
+ && mBypassDnd == that.mBypassDnd
+ && getLockscreenVisibility() == that.getLockscreenVisibility()
+ && mLights == that.mLights
+ && getLightColor() == that.getLightColor()
+ && getUserLockedFields() == that.getUserLockedFields()
+ && isFgServiceShown() == that.isFgServiceShown()
+ && mVibrationEnabled == that.mVibrationEnabled
+ && mShowBadge == that.mShowBadge
+ && isDeleted() == that.isDeleted()
+ && isBlockableSystem() == that.isBlockableSystem()
+ && mAllowAppOverlay == that.mAllowAppOverlay
+ && Objects.equals(getId(), that.getId())
+ && Objects.equals(getName(), that.getName())
+ && Objects.equals(mDesc, that.mDesc)
+ && Objects.equals(getSound(), that.getSound())
+ && Arrays.equals(mVibration, that.mVibration)
+ && Objects.equals(getGroup(), that.getGroup())
+ && Objects.equals(getAudioAttributes(), that.getAudioAttributes())
+ && mImportanceLockedByOEM == that.mImportanceLockedByOEM;
}
@Override
@@ -979,7 +1000,8 @@
getLockscreenVisibility(), getSound(), mLights, getLightColor(),
getUserLockedFields(),
isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
- getAudioAttributes(), isBlockableSystem(), mAllowAppOverlay);
+ getAudioAttributes(), isBlockableSystem(), mAllowAppOverlay,
+ mImportanceLockedByOEM);
result = 31 * result + Arrays.hashCode(mVibration);
return result;
}
@@ -1007,6 +1029,7 @@
+ ", mAudioAttributes=" + mAudioAttributes
+ ", mBlockableSystem=" + mBlockableSystem
+ ", mAllowAppOverlay=" + mAllowAppOverlay
+ + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+ '}';
pw.println(prefix + output);
}
@@ -1033,6 +1056,7 @@
+ ", mAudioAttributes=" + mAudioAttributes
+ ", mBlockableSystem=" + mBlockableSystem
+ ", mAllowAppOverlay=" + mAllowAppOverlay
+ + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+ '}';
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 574ccaf..ee6e1ba 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13772,6 +13772,7 @@
* requires_targeting_p (boolean)
* max_squeeze_remeasure_attempts (int)
* edit_choices_before_sending (boolean)
+ * show_in_heads_up (boolean)
* </pre>
* @see com.android.systemui.statusbar.policy.SmartReplyConstants
* @hide
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index fd2b0fae..63fd563 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -203,7 +203,7 @@
* @param action the action that is just clicked
* @param source the source that provided the action, e.g. SOURCE_FROM_APP
*/
- public void onActionClicked(@NonNull String key, @NonNull Notification.Action action,
+ public void onActionInvoked(@NonNull String key, @NonNull Notification.Action action,
@Source int source) {
}
@@ -338,7 +338,7 @@
args.arg1 = key;
args.arg2 = action;
args.argi2 = source;
- mHandler.obtainMessage(MyHandler.MSG_ON_ACTION_CLICKED, args).sendToTarget();
+ mHandler.obtainMessage(MyHandler.MSG_ON_ACTION_INVOKED, args).sendToTarget();
}
}
@@ -349,7 +349,7 @@
public static final int MSG_ON_NOTIFICATION_EXPANSION_CHANGED = 4;
public static final int MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT = 5;
public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6;
- public static final int MSG_ON_ACTION_CLICKED = 7;
+ public static final int MSG_ON_ACTION_INVOKED = 7;
public MyHandler(Looper looper) {
super(looper, null, false);
@@ -419,13 +419,13 @@
onSuggestedReplySent(key, reply, source);
break;
}
- case MSG_ON_ACTION_CLICKED: {
+ case MSG_ON_ACTION_INVOKED: {
SomeArgs args = (SomeArgs) msg.obj;
String key = (String) args.arg1;
Notification.Action action = (Notification.Action) args.arg2;
int source = args.argi2;
args.recycle();
- onActionClicked(key, action, source);
+ onActionInvoked(key, action, source);
break;
}
}
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index ad10cc9..3a644d4 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -22,16 +22,21 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
/**
* Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
* the status bar and any {@link android.service.notification.NotificationListenerService}s.
*/
public class StatusBarNotification implements Parcelable {
+ static final int MAX_LOG_TAG_LENGTH = 36;
+
@UnsupportedAppUsage
private final String pkg;
@UnsupportedAppUsage
@@ -56,6 +61,9 @@
private Context mContext; // used for inflation & icon expansion
+ // Contains the basic logging data of the notification.
+ private LogMaker mLogMaker;
+
/** @hide */
public StatusBarNotification(String pkg, String opPkg, int id,
String tag, int uid, int initialPid, Notification notification, UserHandle user,
@@ -381,4 +389,51 @@
}
return mContext;
}
+
+ /**
+ * Returns a LogMaker that contains all basic information of the notification.
+ * @hide
+ */
+ public LogMaker getLogMaker() {
+ if (mLogMaker == null) {
+ // Initialize fields that only change on update (so a new record).
+ mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
+ .setPackageName(getPackageName())
+ .addTaggedData(MetricsEvent.NOTIFICATION_ID, getId())
+ .addTaggedData(MetricsEvent.NOTIFICATION_TAG, getTag())
+ .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
+ }
+ // Reset fields that can change between updates, or are used by multiple logs.
+ return mLogMaker
+ .clearCategory()
+ .clearType()
+ .clearSubtype()
+ .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
+ .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
+ getNotification().isGroupSummary() ? 1 : 0);
+ }
+
+ private String getGroupLogTag() {
+ return shortenTag(getGroup());
+ }
+
+ private String getChannelIdLogTag() {
+ if (notification.getChannelId() == null) {
+ return null;
+ }
+ return shortenTag(notification.getChannelId());
+ }
+
+ // Make logTag with max size MAX_LOG_TAG_LENGTH.
+ // For shorter or equal tags, returns the tag.
+ // For longer tags, truncate the tag and append a hash of the full tag to
+ // fill the maximum size.
+ private String shortenTag(String logTag) {
+ if (logTag == null || logTag.length() <= MAX_LOG_TAG_LENGTH) {
+ return logTag;
+ }
+ String hash = Integer.toHexString(logTag.hashCode());
+ return logTag.substring(0, MAX_LOG_TAG_LENGTH - hash.length() - 1) + "-"
+ + hash;
+ }
}
diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto
new file mode 100644
index 0000000..b70bb67
--- /dev/null
+++ b/core/proto/android/server/connectivity/data_stall_event.proto
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package com.android.server.connectivity;
+option java_multiple_files = true;
+option java_outer_classname = "DataStallEventProto";
+
+enum ProbeResult {
+ UNKNOWN = 0;
+ VALID = 1;
+ INVALID = 2;
+ PORTAL = 3;
+}
+
+enum ApBand {
+ AP_BAND_UNKNOWN = 0;
+ AP_BAND_2GHZ = 1;
+ AP_BAND_5GHZ = 2;
+}
+
+// Refer to definition in ServiceState.java.
+enum RadioTech {
+ RADIO_TECHNOLOGY_UNKNOWN = 0;
+ RADIO_TECHNOLOGY_GPRS = 1;
+ RADIO_TECHNOLOGY_EDGE = 2;
+ RADIO_TECHNOLOGY_UMTS = 3;
+ RADIO_TECHNOLOGY_IS95A = 4;
+ RADIO_TECHNOLOGY_IS95B = 5;
+ RADIO_TECHNOLOGY_1xRTT = 6;
+ RADIO_TECHNOLOGY_EVDO_0 = 7;
+ RADIO_TECHNOLOGY_EVDO_A = 8;
+ RADIO_TECHNOLOGY_HSDPA = 9;
+ RADIO_TECHNOLOGY_HSUPA = 10;
+ RADIO_TECHNOLOGY_HSPA = 11;
+ RADIO_TECHNOLOGY_EVDO_B = 12;
+ RADIO_TECHNOLOGY_EHRPD = 13;
+ RADIO_TECHNOLOGY_LTE = 14;
+ RADIO_TECHNOLOGY_HSPAP = 15;
+ RADIO_TECHNOLOGY_GSM = 16;
+ RADIO_TECHNOLOGY_TD_SCDMA = 17;
+ RADIO_TECHNOLOGY_IWLAN = 18;
+ RADIO_TECHNOLOGY_LTE_CA = 19;
+ RADIO_TECHNOLOGY_NR = 20;
+}
+
+// Cellular specific information.
+message CellularData {
+ // Indicate the radio technology at the time of data stall suspected.
+ optional RadioTech rat_type = 1;
+ // True if device is in roaming network at the time of data stall suspected.
+ optional bool is_roaming = 2;
+ // Registered network MccMnc when data stall happen
+ optional string network_mccmnc = 3;
+ // Indicate the SIM card carrier.
+ optional string sim_mccmnc = 4;
+ // Signal strength level at the time of data stall suspected.
+ optional int32 signal_strength = 5;
+}
+
+// Wifi specific information.
+message WifiData {
+ // Signal strength at the time of data stall suspected.
+ // RSSI range is between -55 to -110.
+ optional int32 signal_strength = 1;
+ // AP band.
+ optional ApBand wifi_band = 2;
+}
+
+message DnsEvent {
+ // The dns return code.
+ repeated int32 dns_return_code = 1;
+ // Indicate the timestamp of the dns event.
+ repeated int64 dns_time = 2;
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
new file mode 100644
index 0000000..8a3ba8c
--- /dev/null
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StatusBarNotificationTest {
+
+ private final Context mMockContext = mock(Context.class);
+ @Mock
+ private PackageManager mPm;
+
+ private static final String PKG = "com.example.o";
+ private static final int UID = 9583;
+ private static final int ID = 1;
+ private static final String TAG = "tag1";
+ private static final String CHANNEL_ID = "channel";
+ private static final String CHANNEL_ID_LONG =
+ "give_a_developer_a_string_argument_and_who_knows_what_they_will_pass_in_there";
+ private static final String GROUP_ID_1 = "group1";
+ private static final String GROUP_ID_2 = "group2";
+ private static final String GROUP_ID_LONG =
+ "0|com.foo.bar|g:content://com.foo.bar.ui/account%3A-0000000/account/";
+ private static final android.os.UserHandle USER =
+ UserHandle.of(ActivityManager.getCurrentUser());
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockContext.getResources()).thenReturn(
+ InstrumentationRegistry.getContext().getResources());
+ when(mMockContext.getPackageManager()).thenReturn(mPm);
+ when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ }
+
+ @Test
+ public void testLogMaker() {
+ final LogMaker logMaker = getNotification(PKG, GROUP_ID_1, CHANNEL_ID).getLogMaker();
+
+ assertEquals(CHANNEL_ID,
+ (String) logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID));
+ assertEquals(PKG, logMaker.getPackageName());
+ assertEquals(ID, logMaker.getTaggedData(MetricsEvent.NOTIFICATION_ID));
+ assertEquals(TAG, logMaker.getTaggedData(MetricsEvent.NOTIFICATION_TAG));
+ assertEquals(GROUP_ID_1,
+ logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+ }
+
+ @Test
+ public void testLogMakerNoChannel() {
+ final LogMaker logMaker = getNotification(PKG, GROUP_ID_1, null).getLogMaker();
+
+ assertNull(logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID));
+ }
+
+ @Test
+ public void testLogMakerLongChannel() {
+ final LogMaker logMaker = getNotification(PKG, null, CHANNEL_ID_LONG).getLogMaker();
+ final String loggedId = (String) logMaker
+ .getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID);
+ assertEquals(StatusBarNotification.MAX_LOG_TAG_LENGTH, loggedId.length());
+ assertEquals(CHANNEL_ID_LONG.substring(0, 10), loggedId.substring(0, 10));
+ }
+
+ @Test
+ public void testLogMakerNoGroup() {
+ final LogMaker logMaker = getNotification(PKG, null, CHANNEL_ID).getLogMaker();
+
+ assertNull(
+ logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+ }
+
+ @Test
+ public void testLogMakerLongGroup() {
+ final LogMaker logMaker = getNotification(PKG, GROUP_ID_LONG, CHANNEL_ID)
+ .getLogMaker();
+
+ final String loggedId = (String)
+ logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID);
+ assertEquals(StatusBarNotification.MAX_LOG_TAG_LENGTH, loggedId.length());
+ assertEquals(GROUP_ID_LONG.substring(0, 10), loggedId.substring(0, 10));
+ }
+
+ @Test
+ public void testLogMakerOverrideGroup() {
+ StatusBarNotification sbn = getNotification(PKG, GROUP_ID_1, CHANNEL_ID);
+ assertEquals(GROUP_ID_1,
+ sbn.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+
+ sbn.setOverrideGroupKey(GROUP_ID_2);
+ assertEquals(GROUP_ID_2,
+ sbn.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+
+ sbn.setOverrideGroupKey(null);
+ assertEquals(GROUP_ID_1,
+ sbn.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+ }
+
+ private StatusBarNotification getNotification(String pkg, String group, String channelId) {
+ final Notification.Builder builder = new Notification.Builder(mMockContext, channelId)
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+ if (group != null) {
+ builder.setGroup(group);
+ }
+
+ Notification n = builder.build();
+ return new StatusBarNotification(
+ pkg, pkg, ID, TAG, UID, UID, n, USER, null, UID);
+ }
+
+}
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 1ee851f..7b20f7a 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -250,7 +250,8 @@
if (controllerInfo.mAllowedCommands == null) {
// For trusted apps, send non-null allowed commands to keep
// connection.
- controllerInfo.mAllowedCommands = new Session2CommandGroup();
+ controllerInfo.mAllowedCommands =
+ new Session2CommandGroup.Builder().build();
}
if (DEBUG) {
Log.d(TAG, "Accepting connection: " + controllerInfo);
diff --git a/media/java/android/media/Session2CommandGroup.java b/media/java/android/media/Session2CommandGroup.java
index 4668ec4..519888e 100644
--- a/media/java/android/media/Session2CommandGroup.java
+++ b/media/java/android/media/Session2CommandGroup.java
@@ -35,7 +35,6 @@
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
* </p>
- * @hide
*/
public final class Session2CommandGroup implements Parcelable {
private static final String TAG = "Session2CommandGroup";
@@ -56,16 +55,11 @@
Set<Session2Command> mCommands = new HashSet<>();
/**
- * Default Constructor.
- */
- public Session2CommandGroup() {}
-
- /**
* Creates a new Session2CommandGroup with commands copied from another object.
*
* @param commands The collection of commands to copy.
*/
- public Session2CommandGroup(@Nullable Collection<Session2Command> commands) {
+ Session2CommandGroup(@Nullable Collection<Session2Command> commands) {
if (commands != null) {
mCommands.addAll(commands);
}
@@ -129,7 +123,10 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (dest == null) {
+ throw new IllegalArgumentException("parcel shouldn't be null");
+ }
dest.writeParcelableArray(mCommands.toArray(new Session2Command[0]), 0);
}
@@ -149,6 +146,9 @@
* @param commandGroup
*/
public Builder(@NonNull Session2CommandGroup commandGroup) {
+ if (commandGroup == null) {
+ throw new IllegalArgumentException("command group shouldn't be null");
+ }
mCommands = commandGroup.getCommands();
}
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index fab1bcc..838b88b 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -382,11 +382,11 @@
}
@Override
- public void onActionClicked(@NonNull String key, @NonNull Notification.Action action,
+ public void onActionInvoked(@NonNull String key, @NonNull Notification.Action action,
@Source int source) {
if (DEBUG) {
Log.d(TAG,
- "onActionClicked() called with: key = [" + key + "], action = [" + action.title
+ "onActionInvoked() called with: key = [" + key + "], action = [" + action.title
+ "], source = [" + source + "]");
}
mSmartActionsHelper.onActionClicked(key, action, source);
diff --git a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
index 71fd9ce..acf1180 100644
--- a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
+++ b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
@@ -27,7 +27,9 @@
import android.app.NotificationChannel;
import android.app.Person;
import android.app.RemoteInput;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioSystem;
import android.os.Build;
@@ -67,8 +69,12 @@
private boolean isPreChannelsNotification() {
try {
- mTargetSdkVersion = mPackageManager.getApplicationInfo(
- mSbn.getPackageName(), 0, mSbn.getUserId()).targetSdkVersion;
+ ApplicationInfo info = mPackageManager.getApplicationInfo(
+ mSbn.getPackageName(), PackageManager.MATCH_ALL,
+ mSbn.getUserId());
+ if (info != null) {
+ mTargetSdkVersion = info.targetSdkVersion;
+ }
} catch (RemoteException e) {
Log.w(TAG, "Couldn't look up " + mSbn.getPackageName());
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7cc5524..476089a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -454,6 +454,10 @@
RemoteInput.Builder.setEditChoicesBeforeSending. -->
<bool name="config_smart_replies_in_notifications_edit_choices_before_sending">false</bool>
+ <!-- Smart replies in notifications: Whether smart suggestions in notifications are enabled in
+ heads-up notifications. -->
+ <bool name="config_smart_replies_in_notifications_show_in_heads_up">true</bool>
+
<!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents.
Blank sends the user to the Chooser first.
This name is in the ComponentName flattened format (package/class) -->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 607d96d..6df72fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -20,11 +20,13 @@
.USER_SENTIMENT_NEGATIVE;
import android.content.Context;
+import android.metrics.LogMaker;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -58,6 +60,8 @@
*/
private boolean mIsShadeExpanded;
+ private MetricsLogger mMetricsLogger = new MetricsLogger();
+
@Inject
public NotificationBlockingHelperManager(Context context) {
mContext = context;
@@ -100,6 +104,11 @@
mBlockingHelperRow = row;
mBlockingHelperRow.setBlockingHelperShowing(true);
+ // Log triggering of blocking helper by the system. This log line
+ // should be emitted before the "display" log line.
+ mMetricsLogger.write(
+ getLogMaker().setSubtype(MetricsEvent.BLOCKING_HELPER_TRIGGERED_BY_SYSTEM));
+
// We don't care about the touch origin (x, y) since we're opening guts without any
// explicit user interaction.
manager.openGuts(mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
@@ -153,6 +162,13 @@
|| mNonBlockablePkgs.contains(makeChannelKey(packageName, channelName));
}
+ private LogMaker getLogMaker() {
+ return mBlockingHelperRow.getStatusBarNotification()
+ .getLogMaker()
+ .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER);
+ }
+
// Format must stay in sync with frameworks/base/core/res/res/values/config.xml
// config_nonBlockableNotificationPackages
private String makeChannelKey(String pkg, String channel) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index bf30cf9..c161da3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1489,7 +1489,7 @@
}
}
}
- if (mHeadsUpChild != null) {
+ if (mHeadsUpChild != null && mSmartReplyConstants.getShowInHeadsUp()) {
mHeadsUpSmartReplyView =
applySmartReplyView(mHeadsUpChild, smartRepliesAndActions, entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index b1eab80..5253e38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -123,11 +123,15 @@
private OnClickListener mOnKeepShowing = v -> {
mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
closeControls(v);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT));
};
private OnClickListener mOnToggleSilent = v -> {
Runnable saveImportance = () -> {
swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_ALERT_ME));
};
if (mCheckSaveListener != null) {
mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -139,6 +143,8 @@
private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Runnable saveImportance = () -> {
swapContent(ACTION_BLOCK, true /* animate */);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED));
};
if (mCheckSaveListener != null) {
mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -153,6 +159,8 @@
logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
swapContent(ACTION_UNDO, true /* animate */);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO));
};
public NotificationInfo(Context context, AttributeSet attrs) {
@@ -251,6 +259,9 @@
bindHeader();
bindPrompt();
bindButtons();
+
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_DISPLAY));
}
private void bindHeader() throws RemoteException {
@@ -588,6 +599,8 @@
confirmation.setAlpha(1f);
header.setVisibility(VISIBLE);
header.setAlpha(1f);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_DISMISS));
}
@Override
@@ -733,4 +746,8 @@
}
}
}
+
+ private LogMaker getLogMaker() {
+ return mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 0c63e29..3bd0d45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -45,16 +45,19 @@
"max_squeeze_remeasure_attempts";
private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
"edit_choices_before_sending";
+ private static final String KEY_SHOW_IN_HEADS_UP = "show_in_heads_up";
private final boolean mDefaultEnabled;
private final boolean mDefaultRequiresP;
private final int mDefaultMaxSqueezeRemeasureAttempts;
private final boolean mDefaultEditChoicesBeforeSending;
+ private final boolean mDefaultShowInHeadsUp;
private boolean mEnabled;
private boolean mRequiresTargetingP;
private int mMaxSqueezeRemeasureAttempts;
private boolean mEditChoicesBeforeSending;
+ private boolean mShowInHeadsUp;
private final Context mContext;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -73,6 +76,8 @@
R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts);
mDefaultEditChoicesBeforeSending = resources.getBoolean(
R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
+ mDefaultShowInHeadsUp = resources.getBoolean(
+ R.bool.config_smart_replies_in_notifications_show_in_heads_up);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -99,6 +104,7 @@
KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts);
mEditChoicesBeforeSending = mParser.getBoolean(
KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
+ mShowInHeadsUp = mParser.getBoolean(KEY_SHOW_IN_HEADS_UP, mDefaultShowInHeadsUp);
}
}
@@ -142,4 +148,11 @@
return mEditChoicesBeforeSending;
}
}
+
+ /**
+ * Returns whether smart suggestions should be enabled in heads-up notifications.
+ */
+ public boolean getShowInHeadsUp() {
+ return mShowInHeadsUp;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index ecb0cf8..f6791dd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -45,7 +45,6 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
@@ -73,6 +72,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -498,12 +498,15 @@
}
@Test
- public void testLogBlockingHelperCounter_doesntLogForNormalGutsView() throws Exception {
+ public void testLogBlockingHelperCounter_logGutsViewDisplayed() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
IMPORTANCE_DEFAULT);
mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
- verifyZeroInteractions(mMetricsLogger);
+ verify(mMetricsLogger).write(argThat(logMaker ->
+ logMaker.getType() == MetricsEvent.NOTIFICATION_BLOCKING_HELPER
+ && logMaker.getSubtype() == MetricsEvent.BLOCKING_HELPER_DISPLAY
+ ));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 37a56a3..3cbf902 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -54,6 +54,7 @@
R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts, 7);
resources.addOverride(
R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false);
+ resources.addOverride(R.bool.config_smart_replies_in_notifications_show_in_heads_up, true);
mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
}
@@ -152,6 +153,26 @@
RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
}
+ @Test
+ public void testShowInHeadsUpWithNoConfig() {
+ assertTrue(mConstants.isEnabled());
+ assertTrue(mConstants.getShowInHeadsUp());
+ }
+
+ @Test
+ public void testShowInHeadsUpEnabled() {
+ overrideSetting("enabled=true,show_in_heads_up=true");
+ triggerConstantsOnChange();
+ assertTrue(mConstants.getShowInHeadsUp());
+ }
+
+ @Test
+ public void testShowInHeadsUpDisabled() {
+ overrideSetting("enabled=true,show_in_heads_up=false");
+ triggerConstantsOnChange();
+ assertFalse(mConstants.getShowInHeadsUp());
+ }
+
private void overrideSetting(String flags) {
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags);
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index aff5e13..5b45a08 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -213,6 +213,25 @@
IOOP_SYNC = 4;
}
+ // Subtypes of notifications blocking helper view
+ // (NOTIFICATION_BLOCKING_HELPER).
+ enum NotificationBlockingHelper {
+ BLOCKING_HELPER_UNKNOWN = 0;
+ BLOCKING_HELPER_DISPLAY = 1;
+ BLOCKING_HELPER_DISMISS = 2;
+ // When the view of the notification blocking helper was triggered by
+ // system.
+ BLOCKING_HELPER_TRIGGERED_BY_SYSTEM = 3;
+ // "block" was clicked.
+ BLOCKING_HELPER_CLICK_BLOCKED = 4;
+ // "stay silent" was clicked.
+ BLOCKING_HELPER_CLICK_STAY_SILENT = 5;
+ // "alert me" was clicked.
+ BLOCKING_HELPER_CLICK_ALERT_ME = 6;
+ // "undo" was clicked (enables the user to undo "stop notification" action).
+ BLOCKING_HELPER_CLICK_UNDO = 7;
+ }
+
// Known visual elements: views or controls.
enum View {
// Unknown view
@@ -6768,6 +6787,13 @@
// OS: Q
ACCESSIBILITY_VIBRATION_RING = 1620;
+ // ACTION: Notification blocking helper view, which helps the user to block
+ // application or channel from showing notifications.
+ // SUBTYPE: NotificationBlockingHelper enum.
+ // CATEGORY: NOTIFICATION
+ // OS: Q
+ NOTIFICATION_BLOCKING_HELPER = 1621;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 17bf570..6a97fbb 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -32,7 +32,6 @@
import android.text.TextUtils;
import android.util.MathUtils;
import android.util.Slog;
-import android.util.SparseArray;
import android.view.Display;
import android.view.MagnificationSpec;
import android.view.View;
@@ -40,8 +39,6 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.wm.WindowManagerInternal;
@@ -63,7 +60,7 @@
private static final String LOG_TAG = "MagnificationController";
public static final float MIN_SCALE = 1.0f;
- public static final float MAX_SCALE = 5.0f;
+ public static final float MAX_SCALE = 8.0f;
private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 12e7376..80049e8 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -115,8 +115,11 @@
private static final boolean DEBUG_PANNING_SCALING = false || DEBUG_ALL;
private static final boolean DEBUG_EVENT_STREAM = false || DEBUG_ALL;
+ // The MIN_SCALE is different from MagnificationController.MIN_SCALE due
+ // to AccessibilityService.MagnificationController#setScale() has
+ // different scale range
private static final float MIN_SCALE = 2.0f;
- private static final float MAX_SCALE = 5.0f;
+ private static final float MAX_SCALE = MagnificationController.MAX_SCALE;
@VisibleForTesting final MagnificationController mMagnificationController;
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index a9f190c..62da3f8 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -34,9 +34,9 @@
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.StatsLog;
import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.PackageManagerService;
import java.io.File;
@@ -179,6 +179,7 @@
}
private static void executeRescueLevelInternal(Context context, int level) throws Exception {
+ StatsLog.write(StatsLog.RESCUE_PARTY_RESET_REPORTED, level);
switch (level) {
case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index bab9a65..0ff2a09 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -739,7 +739,7 @@
private void stopServiceLocked(ServiceRecord service) {
if (service.delayed) {
- // If service isn't actually running, but is is being held in the
+ // If service isn't actually running, but is being held in the
// delayed list, then we need to keep it started but note that it
// should be stopped once no longer delayed.
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Delaying stop of pending: " + service);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7ecdad2..7b7a3ec 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1581,6 +1581,9 @@
mIsAutomotive =
mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0);
+
+ mPreferencesHelper.lockChannelsForOEM(getContext().getResources().getStringArray(
+ com.android.internal.R.array.config_nonBlockableNotificationPackages));
}
@Override
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 9942f59..5598741 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -91,7 +91,6 @@
public final class NotificationRecord {
static final String TAG = "NotificationRecord";
static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
- private static final int MAX_LOGTAG_LENGTH = 35;
// the period after which a notification is updated where it can make sound
private static final int MAX_SOUND_DELAY_MS = 2000;
final StatusBarNotification sbn;
@@ -162,10 +161,7 @@
private ArrayList<String> mPeopleOverride;
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
private boolean mShowBadge;
- private LogMaker mLogMaker;
private Light mLight;
- private String mGroupLogTag;
- private String mChannelIdLogTag;
/**
* This list contains system generated smart actions from NAS, app-generated smart actions are
* stored in Notification.actions with isContextual() set to true.
@@ -789,7 +785,8 @@
mImportanceExplanation = "user";
}
if (!getChannel().hasUserSetImportance()
- && mAssistantImportance != IMPORTANCE_UNSPECIFIED) {
+ && mAssistantImportance != IMPORTANCE_UNSPECIFIED
+ && !getChannel().isImportanceLockedByOEM()) {
mImportance = mAssistantImportance;
mImportanceExplanation = "asst";
}
@@ -969,33 +966,6 @@
public void setOverrideGroupKey(String overrideGroupKey) {
sbn.setOverrideGroupKey(overrideGroupKey);
- mGroupLogTag = null;
- }
-
- private String getGroupLogTag() {
- if (mGroupLogTag == null) {
- mGroupLogTag = shortenTag(sbn.getGroup());
- }
- return mGroupLogTag;
- }
-
- private String getChannelIdLogTag() {
- if (mChannelIdLogTag == null) {
- mChannelIdLogTag = shortenTag(mChannel.getId());
- }
- return mChannelIdLogTag;
- }
-
- private String shortenTag(String longTag) {
- if (longTag == null) {
- return null;
- }
- if (longTag.length() < MAX_LOGTAG_LENGTH) {
- return longTag;
- } else {
- return longTag.substring(0, MAX_LOGTAG_LENGTH - 8) + "-" +
- Integer.toHexString(longTag.hashCode());
- }
}
public NotificationChannel getChannel() {
@@ -1272,24 +1242,9 @@
}
public LogMaker getLogMaker(long now) {
- if (mLogMaker == null) {
- // initialize fields that only change on update (so a new record)
- mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
- .setPackageName(sbn.getPackageName())
- .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId())
- .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag())
- .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
- }
- // reset fields that can change between updates, or are used by multiple logs
- return mLogMaker
- .clearCategory()
- .clearType()
- .clearSubtype()
+ return sbn.getLogMaker()
.clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX)
.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
- .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
- .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
- sbn.getNotification().isGroupSummary() ? 1 : 0)
.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now))
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 7c0e0b0..28f6972 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -68,6 +68,7 @@
private static final String TAG = "NotificationPrefHelper";
private static final int XML_VERSION = 1;
private static final int UNKNOWN_UID = UserHandle.USER_NULL;
+ private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
@VisibleForTesting
static final String TAG_RANKING = "ranking";
@@ -94,6 +95,7 @@
private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
private static final boolean DEFAULT_SHOW_BADGE = true;
private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true;
+ private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false;
/**
* Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
* fields.
@@ -621,6 +623,12 @@
channel.setLockscreenVisibility(r.visibility);
}
clearLockedFields(channel);
+ channel.setImportanceLockedByOEM(r.oemLockedImportance);
+ if (!channel.isImportanceLockedByOEM()) {
+ if (r.futureOemLockedChannels.remove(channel.getId())) {
+ channel.setImportanceLockedByOEM(true);
+ }
+ }
if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
channel.setLockscreenVisibility(
NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
@@ -664,6 +672,12 @@
} else {
updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
}
+ // no importance updates are allowed if OEM blocked it
+ updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
+ if (updatedChannel.isImportanceLockedByOEM()) {
+ updatedChannel.setImportance(channel.getImportance());
+ }
+
r.channels.put(updatedChannel.getId(), updatedChannel);
if (onlyHasDefaultChannel(pkg, uid)) {
@@ -753,6 +767,44 @@
}
}
+ public void lockChannelsForOEM(String[] appOrChannelList) {
+ if (appOrChannelList == null) {
+ return;
+ }
+ for (String appOrChannel : appOrChannelList) {
+ if (!TextUtils.isEmpty(appOrChannel)) {
+ String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
+ if (appSplit != null && appSplit.length > 0) {
+ String appName = appSplit[0];
+ String channelId = appSplit.length == 2 ? appSplit[1] : null;
+
+ synchronized (mPackagePreferences) {
+ for (PackagePreferences r : mPackagePreferences.values()) {
+ if (r.pkg.equals(appName)) {
+ if (channelId == null) {
+ // lock all channels for the app
+ r.oemLockedImportance = true;
+ for (NotificationChannel channel : r.channels.values()) {
+ channel.setImportanceLockedByOEM(true);
+ }
+ } else {
+ NotificationChannel channel = r.channels.get(channelId);
+ if (channel != null) {
+ channel.setImportanceLockedByOEM(true);
+ } else {
+ // if this channel shows up in the future, make sure it'll
+ // be locked immediately
+ r.futureOemLockedChannels.add(channelId);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
int uid, String groupId, boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
@@ -1604,6 +1656,8 @@
boolean showBadge = DEFAULT_SHOW_BADGE;
boolean appOverlay = DEFAULT_ALLOW_APP_OVERLAY;
int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
+ boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
+ List<String> futureOemLockedChannels = new ArrayList<>();
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 7f1fb6c..aa9bc26 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -313,6 +313,10 @@
Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED, 1) == 0) {
return launcherActivities;
}
+ if (launcherActivities == null) {
+ // Cannot access profile, so we don't even return any hidden apps.
+ return null;
+ }
final int callingUid = injectBinderCallingUid();
final ArrayList<ResolveInfo> result = new ArrayList<>(launcherActivities.getList());
diff --git a/services/core/java/com/android/server/rollback/PackageRollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
similarity index 65%
rename from services/core/java/com/android/server/rollback/PackageRollbackData.java
rename to services/core/java/com/android/server/rollback/RollbackData.java
index 15d1242..f0589aa 100644
--- a/services/core/java/com/android/server/rollback/PackageRollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -20,17 +20,21 @@
import java.io.File;
import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
/**
- * Information about a rollback available for a particular package.
- * This is similar to {@link PackageRollbackInfo}, but extended with
- * additional information for internal bookkeeping.
+ * Information about a rollback available for a set of atomically installed
+ * packages.
*/
-class PackageRollbackData {
- public final PackageRollbackInfo info;
+class RollbackData {
+ /**
+ * The per-package rollback information.
+ */
+ public final List<PackageRollbackInfo> packages = new ArrayList<>();
/**
- * The directory where the apk backup is stored.
+ * The directory where the rollback data is stored.
*/
public final File backupDir;
@@ -38,12 +42,9 @@
* The time when the upgrade occurred, for purposes of expiring
* rollback data.
*/
- public final Instant timestamp;
+ public Instant timestamp;
- PackageRollbackData(PackageRollbackInfo info,
- File backupDir, Instant timestamp) {
- this.info = info;
+ RollbackData(File backupDir) {
this.backupDir = backupDir;
- this.timestamp = timestamp;
}
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 0c21312..8021265 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -63,9 +63,11 @@
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -86,10 +88,20 @@
// mLock is held when they are called.
private final Object mLock = new Object();
+ // Package rollback data for rollback-enabled installs that have not yet
+ // been committed. Maps from sessionId to rollback data.
+ @GuardedBy("mLock")
+ private final Map<Integer, RollbackData> mPendingRollbacks = new HashMap<>();
+
+ // Map from child session id's for enabled rollbacks to their
+ // corresponding parent session ids.
+ @GuardedBy("mLock")
+ private final Map<Integer, Integer> mChildSessions = new HashMap<>();
+
// Package rollback data available to be used for rolling back a package.
// This list is null until the rollback data has been loaded.
@GuardedBy("mLock")
- private List<PackageRollbackData> mAvailableRollbacks;
+ private List<RollbackData> mAvailableRollbacks;
// The list of recently executed rollbacks.
// This list is null until the rollback data has been loaded.
@@ -102,14 +114,25 @@
// to store this data:
// /data/rollback/
// available/
- // com.package.A-XXX/
- // base.apk
- // rollback.json
- // com.package.B-YYY/
- // base.apk
- // rollback.json
+ // XXX/
+ // com.package.A/
+ // base.apk
+ // info.json
+ // enabled.txt
+ // YYY/
+ // com.package.B/
+ // base.apk
+ // info.json
+ // enabled.txt
// recently_executed.json
- // TODO: Use AtomicFile for rollback.json and recently_executed.json.
+ //
+ // * XXX, YYY are random strings from Files.createTempDirectory
+ // * info.json contains the package version to roll back from/to.
+ // * enabled.txt contains a timestamp for when the rollback was first
+ // made available. This file is not written until the rollback is made
+ // available.
+ //
+ // TODO: Use AtomicFile for all the .json files?
private final File mRollbackDataDir;
private final File mAvailableRollbacksDir;
private final File mRecentlyExecutedRollbacksFile;
@@ -135,6 +158,9 @@
// expiration.
getHandler().post(() -> ensureRollbackDataLoaded());
+ PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
+ installer.registerSessionCallback(new SessionCallback(), getHandler());
+
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
@@ -199,27 +225,23 @@
android.Manifest.permission.MANAGE_ROLLBACKS,
"getAvailableRollback");
- PackageRollbackInfo.PackageVersion installedVersion =
- getInstalledPackageVersion(packageName);
- if (installedVersion == null) {
+ RollbackData data = getRollbackForPackage(packageName);
+ if (data == null) {
return null;
}
- synchronized (mLock) {
- // TODO: Have ensureRollbackDataLoadedLocked return the list of
- // available rollbacks, to hopefully avoid forgetting to call it?
- ensureRollbackDataLoadedLocked();
- for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
- PackageRollbackData data = mAvailableRollbacks.get(i);
- if (data.info.packageName.equals(packageName)
- && data.info.higherVersion.equals(installedVersion)) {
- // TODO: For atomic installs, check all dependent packages
- // for available rollbacks and include that info here.
- return new RollbackInfo(data.info);
- }
+ // Note: The rollback for the package ought to be for the currently
+ // installed version, otherwise the rollback data is out of date. In
+ // that rare case, we'll check when we execute the rollback whether
+ // it's out of date or not, so no need to check package versions here.
+
+ for (PackageRollbackInfo info : data.packages) {
+ if (info.packageName.equals(packageName)) {
+ // TODO: Once the RollbackInfo API supports info about
+ // dependant packages, add that info here.
+ return new RollbackInfo(info);
}
}
-
return null;
}
@@ -229,16 +251,14 @@
android.Manifest.permission.MANAGE_ROLLBACKS,
"getPackagesWithAvailableRollbacks");
- // TODO: This may return packages whose rollback is out of date or
- // expired. Presumably that's okay because the package rollback could
- // be expired anyway between when the caller calls this method and
- // when the caller calls getAvailableRollback for more details.
final Set<String> packageNames = new HashSet<>();
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
- PackageRollbackData data = mAvailableRollbacks.get(i);
- packageNames.add(data.info.packageName);
+ RollbackData data = mAvailableRollbacks.get(i);
+ for (PackageRollbackInfo info : data.packages) {
+ packageNames.add(info.packageName);
+ }
}
}
return new StringParceledListSlice(new ArrayList<>(packageNames));
@@ -279,47 +299,49 @@
*/
private void executeRollbackInternal(RollbackInfo rollback,
String callerPackageName, IntentSender statusReceiver) {
- String packageName = rollback.targetPackage.packageName;
- Log.i(TAG, "Initiating rollback of " + packageName);
+ String targetPackageName = rollback.targetPackage.packageName;
+ Log.i(TAG, "Initiating rollback of " + targetPackageName);
- PackageRollbackInfo.PackageVersion installedVersion =
- getInstalledPackageVersion(packageName);
- if (installedVersion == null) {
- // TODO: Test this case
- sendFailure(statusReceiver, "Target package to roll back is not installed");
+ // Get the latest RollbackData for the target package.
+ RollbackData data = getRollbackForPackage(targetPackageName);
+ if (data == null) {
+ sendFailure(statusReceiver, "No rollback available for package.");
return;
}
- if (!rollback.targetPackage.higherVersion.equals(installedVersion)) {
- // TODO: Test this case
- sendFailure(statusReceiver, "Target package version to roll back not installed.");
- return;
+ // Verify the latest rollback matches the version requested.
+ // TODO: Check dependant packages too once RollbackInfo includes that
+ // information.
+ for (PackageRollbackInfo info : data.packages) {
+ if (info.packageName.equals(targetPackageName)
+ && !rollback.targetPackage.higherVersion.equals(info.higherVersion)) {
+ sendFailure(statusReceiver, "Rollback is out of date.");
+ return;
+ }
}
+ // Verify the RollbackData is up to date with what's installed on
+ // device.
// TODO: We assume that between now and the time we commit the
// downgrade install, the currently installed package version does not
// change. This is not safe to assume, particularly in the case of a
// rollback racing with a roll-forward fix of a buggy package.
// Figure out how to ensure we don't commit the rollback if
// roll forward happens at the same time.
- PackageRollbackData data = null;
- synchronized (mLock) {
- ensureRollbackDataLoadedLocked();
- for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
- PackageRollbackData available = mAvailableRollbacks.get(i);
- // TODO: Check if available.info.lowerVersion matches
- // rollback.targetPackage.lowerVersion?
- if (available.info.packageName.equals(packageName)
- && available.info.higherVersion.equals(installedVersion)) {
- data = available;
- break;
- }
+ for (PackageRollbackInfo info : data.packages) {
+ PackageRollbackInfo.PackageVersion installedVersion =
+ getInstalledPackageVersion(info.packageName);
+ if (installedVersion == null) {
+ // TODO: Test this case
+ sendFailure(statusReceiver, "Package to roll back is not installed");
+ return;
}
- }
- if (data == null) {
- sendFailure(statusReceiver, "Rollback not available");
- return;
+ if (!info.higherVersion.equals(installedVersion)) {
+ // TODO: Test this case
+ sendFailure(statusReceiver, "Package version to roll back not installed.");
+ return;
+ }
}
// Get a context for the caller to use to install the downgraded
@@ -334,29 +356,39 @@
PackageManager pm = context.getPackageManager();
try {
- PackageInstaller.Session session = null;
-
PackageInstaller packageInstaller = pm.getPackageInstaller();
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- params.setAllowDowngrade(true);
- int sessionId = packageInstaller.createSession(params);
- session = packageInstaller.openSession(sessionId);
+ parentParams.setAllowDowngrade(true);
+ parentParams.setMultiPackage();
+ int parentSessionId = packageInstaller.createSession(parentParams);
+ PackageInstaller.Session parentSession = packageInstaller.openSession(parentSessionId);
- // TODO: Will it always be called "base.apk"? What about splits?
- File baseApk = new File(data.backupDir, "base.apk");
- try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(baseApk,
- ParcelFileDescriptor.MODE_READ_ONLY)) {
- final long token = Binder.clearCallingIdentity();
- try {
- session.write("base.apk", 0, baseApk.length(), fd);
- } finally {
- Binder.restoreCallingIdentity(token);
+ for (PackageRollbackInfo info : data.packages) {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setAllowDowngrade(true);
+ int sessionId = packageInstaller.createSession(params);
+ PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+
+ // TODO: Will it always be called "base.apk"? What about splits?
+ // What about apex?
+ File packageDir = new File(data.backupDir, info.packageName);
+ File baseApk = new File(packageDir, "base.apk");
+ try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(baseApk,
+ ParcelFileDescriptor.MODE_READ_ONLY)) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ session.write("base.apk", 0, baseApk.length(), fd);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ parentSession.addChildSessionId(sessionId);
}
final LocalIntentReceiver receiver = new LocalIntentReceiver();
- session.commit(receiver.getIntentSender());
+ parentSession.commit(receiver.getIntentSender());
Intent result = receiver.getResult();
int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
@@ -371,13 +403,14 @@
sendSuccess(statusReceiver);
Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
- Uri.fromParts("package", packageName, Manifest.permission.MANAGE_ROLLBACKS));
+ Uri.fromParts("package", targetPackageName,
+ Manifest.permission.MANAGE_ROLLBACKS));
// TODO: This call emits the warning "Calling a method in the
// system process without a qualified user". Fix that.
mContext.sendBroadcast(broadcast);
} catch (IOException e) {
- Log.e(TAG, "Unable to roll back " + packageName, e);
+ Log.e(TAG, "Unable to roll back " + targetPackageName, e);
sendFailure(statusReceiver, "IOException: " + e.toString());
return;
}
@@ -408,12 +441,15 @@
// testing anyway.
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- Iterator<PackageRollbackData> iter = mAvailableRollbacks.iterator();
+ Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
while (iter.hasNext()) {
- PackageRollbackData data = iter.next();
- if (data.info.packageName.equals(packageName)) {
- iter.remove();
- removeFile(data.backupDir);
+ RollbackData data = iter.next();
+ for (PackageRollbackInfo info : data.packages) {
+ if (info.packageName.equals(packageName)) {
+ iter.remove();
+ removeFile(data.backupDir);
+ break;
+ }
}
}
}
@@ -453,27 +489,41 @@
mAvailableRollbacksDir.mkdirs();
mAvailableRollbacks = new ArrayList<>();
for (File rollbackDir : mAvailableRollbacksDir.listFiles()) {
- if (rollbackDir.isDirectory()) {
- // TODO: How to detect and clean up an invalid rollback
- // directory? We don't know if it's invalid because something
- // went wrong, or if it's only temporarily invalid because
- // it's in the process of being created.
+ File enabledFile = new File(rollbackDir, "enabled.txt");
+ // TODO: Delete any directories without an enabled.txt? That could
+ // potentially delete pending rollback data if reloadPersistedData
+ // is called, though there's no reason besides testing for that to
+ // be called.
+ if (rollbackDir.isDirectory() && enabledFile.isFile()) {
+ RollbackData data = new RollbackData(rollbackDir);
try {
- File jsonFile = new File(rollbackDir, "rollback.json");
- String jsonString = IoUtils.readFileAsString(jsonFile.getAbsolutePath());
- JSONObject jsonObject = new JSONObject(jsonString);
- String packageName = jsonObject.getString("packageName");
- long higherVersionCode = jsonObject.getLong("higherVersionCode");
- long lowerVersionCode = jsonObject.getLong("lowerVersionCode");
- Instant timestamp = Instant.parse(jsonObject.getString("timestamp"));
- PackageRollbackData data = new PackageRollbackData(
- new PackageRollbackInfo(packageName,
- new PackageRollbackInfo.PackageVersion(higherVersionCode),
- new PackageRollbackInfo.PackageVersion(lowerVersionCode)),
- rollbackDir, timestamp);
+ PackageRollbackInfo info = null;
+ for (File packageDir : rollbackDir.listFiles()) {
+ if (packageDir.isDirectory()) {
+ File jsonFile = new File(packageDir, "info.json");
+ String jsonString = IoUtils.readFileAsString(
+ jsonFile.getAbsolutePath());
+ JSONObject jsonObject = new JSONObject(jsonString);
+ String packageName = jsonObject.getString("packageName");
+ long higherVersionCode = jsonObject.getLong("higherVersionCode");
+ long lowerVersionCode = jsonObject.getLong("lowerVersionCode");
+
+ data.packages.add(new PackageRollbackInfo(packageName,
+ new PackageRollbackInfo.PackageVersion(higherVersionCode),
+ new PackageRollbackInfo.PackageVersion(lowerVersionCode)));
+ }
+ }
+
+ if (data.packages.isEmpty()) {
+ throw new IOException("No package rollback info found");
+ }
+
+ String enabledString = IoUtils.readFileAsString(enabledFile.getAbsolutePath());
+ data.timestamp = Instant.parse(enabledString.trim());
mAvailableRollbacks.add(data);
} catch (IOException | JSONException | DateTimeParseException e) {
Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e);
+ removeFile(rollbackDir);
}
}
}
@@ -521,13 +571,16 @@
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- Iterator<PackageRollbackData> iter = mAvailableRollbacks.iterator();
+ Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
while (iter.hasNext()) {
- PackageRollbackData data = iter.next();
- if (data.info.packageName.equals(packageName)
- && !data.info.higherVersion.equals(installedVersion)) {
- iter.remove();
- removeFile(data.backupDir);
+ RollbackData data = iter.next();
+ for (PackageRollbackInfo info : data.packages) {
+ if (info.packageName.equals(packageName)
+ && !info.higherVersion.equals(installedVersion)) {
+ iter.remove();
+ removeFile(data.backupDir);
+ break;
+ }
}
}
}
@@ -643,9 +696,9 @@
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
- Iterator<PackageRollbackData> iter = mAvailableRollbacks.iterator();
+ Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
while (iter.hasNext()) {
- PackageRollbackData data = iter.next();
+ RollbackData data = iter.next();
if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) {
iter.remove();
removeFile(data.backupDir);
@@ -673,6 +726,23 @@
return mHandlerThread.getThreadHandler();
}
+ // Returns true if <code>session</code> has installFlags and code path
+ // matching the installFlags and new package code path given to
+ // enableRollback.
+ private boolean sessionMatchesForEnableRollback(PackageInstaller.SessionInfo session,
+ int installFlags, File newPackageCodePath) {
+ if (session == null || session.resolvedBaseCodePath == null) {
+ return false;
+ }
+
+ File packageCodePath = new File(session.resolvedBaseCodePath).getParentFile();
+ if (newPackageCodePath.equals(packageCodePath) && installFlags == session.installFlags) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Called via broadcast by the package manager when a package is being
* staged for install with rollback enabled. Called before the package has
@@ -705,6 +775,34 @@
String packageName = newPackage.packageName;
Log.i(TAG, "Enabling rollback for install of " + packageName);
+ // Figure out the session id associated with this install.
+ int parentSessionId = PackageInstaller.SessionInfo.INVALID_ID;
+ int childSessionId = PackageInstaller.SessionInfo.INVALID_ID;
+ PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
+ for (PackageInstaller.SessionInfo info : installer.getAllSessions()) {
+ if (info.isMultiPackage()) {
+ for (int childId : info.getChildSessionIds()) {
+ PackageInstaller.SessionInfo child = installer.getSessionInfo(childId);
+ if (sessionMatchesForEnableRollback(child, installFlags, newPackageCodePath)) {
+ // TODO: Check we only have one matching session?
+ parentSessionId = info.getSessionId();
+ childSessionId = childId;
+ }
+ }
+ } else {
+ if (sessionMatchesForEnableRollback(info, installFlags, newPackageCodePath)) {
+ // TODO: Check we only have one matching session?
+ parentSessionId = info.getSessionId();
+ childSessionId = parentSessionId;
+ }
+ }
+ }
+
+ if (parentSessionId == PackageInstaller.SessionInfo.INVALID_ID) {
+ Log.e(TAG, "Unable to find session id for enabled rollback.");
+ return false;
+ }
+
PackageRollbackInfo.PackageVersion newVersion =
new PackageRollbackInfo.PackageVersion(newPackage.versionCode);
@@ -720,52 +818,53 @@
PackageRollbackInfo.PackageVersion installedVersion =
new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());
- File backupDir;
+ PackageRollbackInfo info = new PackageRollbackInfo(
+ packageName, newVersion, installedVersion);
+
+ RollbackData data;
try {
- backupDir = Files.createTempDirectory(
- mAvailableRollbacksDir.toPath(), packageName + "-").toFile();
+ synchronized (mLock) {
+ mChildSessions.put(childSessionId, parentSessionId);
+ data = mPendingRollbacks.get(parentSessionId);
+ if (data == null) {
+ File backupDir = Files.createTempDirectory(
+ mAvailableRollbacksDir.toPath(), null).toFile();
+ data = new RollbackData(backupDir);
+ mPendingRollbacks.put(parentSessionId, data);
+ }
+ data.packages.add(info);
+ }
} catch (IOException e) {
Log.e(TAG, "Unable to create rollback for " + packageName, e);
return false;
}
- // TODO: Should the timestamp be for when we commit the install, not
- // when we create the pending one?
- Instant timestamp = Instant.now();
+ File packageDir = new File(data.backupDir, packageName);
+ packageDir.mkdirs();
try {
JSONObject json = new JSONObject();
json.put("packageName", packageName);
json.put("higherVersionCode", newVersion.versionCode);
json.put("lowerVersionCode", installedVersion.versionCode);
- json.put("timestamp", timestamp.toString());
- File jsonFile = new File(backupDir, "rollback.json");
+ File jsonFile = new File(packageDir, "info.json");
PrintWriter pw = new PrintWriter(jsonFile);
pw.println(json.toString());
pw.close();
} catch (IOException | JSONException e) {
Log.e(TAG, "Unable to create rollback for " + packageName, e);
- removeFile(backupDir);
+ removeFile(packageDir);
return false;
}
// TODO: Copy by hard link instead to save on cpu and storage space?
- int status = PackageManagerServiceUtils.copyPackage(installedPackage.codePath, backupDir);
+ int status = PackageManagerServiceUtils.copyPackage(installedPackage.codePath, packageDir);
if (status != PackageManager.INSTALL_SUCCEEDED) {
Log.e(TAG, "Unable to copy package for rollback for " + packageName);
- removeFile(backupDir);
+ removeFile(packageDir);
return false;
}
- PackageRollbackData data = new PackageRollbackData(
- new PackageRollbackInfo(packageName, newVersion, installedVersion),
- backupDir, timestamp);
-
- synchronized (mLock) {
- ensureRollbackDataLoadedLocked();
- mAvailableRollbacks.add(data);
- }
-
return true;
}
@@ -829,4 +928,87 @@
return new PackageRollbackInfo.PackageVersion(pkgInfo.getLongVersionCode());
}
+
+ private class SessionCallback extends PackageInstaller.SessionCallback {
+
+ @Override
+ public void onCreated(int sessionId) { }
+
+ @Override
+ public void onBadgingChanged(int sessionId) { }
+
+ @Override
+ public void onActiveChanged(int sessionId, boolean active) { }
+
+ @Override
+ public void onProgressChanged(int sessionId, float progress) { }
+
+ @Override
+ public void onFinished(int sessionId, boolean success) {
+ RollbackData data = null;
+ synchronized (mLock) {
+ Integer parentSessionId = mChildSessions.remove(sessionId);
+ if (parentSessionId != null) {
+ sessionId = parentSessionId;
+ }
+ data = mPendingRollbacks.remove(sessionId);
+ }
+
+ if (data != null) {
+ if (success) {
+ try {
+ data.timestamp = Instant.now();
+ File enabledFile = new File(data.backupDir, "enabled.txt");
+ PrintWriter pw = new PrintWriter(enabledFile);
+ pw.println(data.timestamp.toString());
+ pw.close();
+
+ synchronized (mLock) {
+ // Note: There is a small window of time between when
+ // the session has been committed by the package
+ // manager and when we make the rollback available
+ // here. Presumably the window is small enough that
+ // nobody will want to roll back the newly installed
+ // package before we make the rollback available.
+ // TODO: We'll lose the rollback data if the
+ // device reboots between when the session is
+ // committed and this point. Revisit this after
+ // adding support for rollback of staged installs.
+ ensureRollbackDataLoadedLocked();
+ mAvailableRollbacks.add(data);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to enable rollback", e);
+ removeFile(data.backupDir);
+ }
+ } else {
+ // The install session was aborted, clean up the pending
+ // install.
+ removeFile(data.backupDir);
+ }
+ }
+ }
+ }
+
+ /*
+ * Returns the RollbackData, if any, for an available rollback that would
+ * roll back the given package. Note: This assumes we have at most one
+ * available rollback for a given package at any one time.
+ */
+ private RollbackData getRollbackForPackage(String packageName) {
+ synchronized (mLock) {
+ // TODO: Have ensureRollbackDataLoadedLocked return the list of
+ // available rollbacks, to hopefully avoid forgetting to call it?
+ ensureRollbackDataLoadedLocked();
+ for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
+ RollbackData data = mAvailableRollbacks.get(i);
+ for (PackageRollbackInfo info : data.packages) {
+ if (info.packageName.equals(packageName)) {
+ return data;
+ }
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/services/tests/rescueparty/Android.bp b/services/tests/rescueparty/Android.bp
new file mode 100644
index 0000000..6733af4
--- /dev/null
+++ b/services/tests/rescueparty/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2008 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.
+
+cc_test {
+ name: "log_rescueparty_reset_event_reported",
+ srcs: ["log_rescueparty_reset_event_reported.cpp"],
+ shared_libs: [
+ "libbase",
+ "libstatslog",
+ ],
+ gtest: false,
+}
diff --git a/services/tests/rescueparty/how_to_run.txt b/services/tests/rescueparty/how_to_run.txt
new file mode 100644
index 0000000..9528d39
--- /dev/null
+++ b/services/tests/rescueparty/how_to_run.txt
@@ -0,0 +1,9 @@
+# Per http://go/westworld-local-development#step3-test-atom-and-metric-locally-on-device ,
+# In one terminal:
+make statsd_testdrive
+./out/host/linux-x86/bin/statsd_testdrive 122
+
+# In another terminal:
+mma -j $(nproc) log_rescueparty_reset_event_reported
+adb push $OUT/testcases/log_rescueparty_reset_event_reported/arm64/log_rescueparty_reset_event_reported /data
+adb shell /data/log_rescueparty_reset_event_reported 1234
diff --git a/services/tests/rescueparty/log_rescueparty_reset_event_reported.cpp b/services/tests/rescueparty/log_rescueparty_reset_event_reported.cpp
new file mode 100644
index 0000000..4aea917
--- /dev/null
+++ b/services/tests/rescueparty/log_rescueparty_reset_event_reported.cpp
@@ -0,0 +1,24 @@
+// Copyright (C) 2008 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.
+
+#include <statslog.h>
+#include <cstdlib>
+
+int main(int argc, const char** argv) {
+ int level = 0;
+ if (argc > 1) {
+ level = std::atoi(argv[1]);
+ }
+ android::util::stats_write(android::util::RESCUE_PARTY_RESET_REPORTED, level);
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 1458266..b9ae7d5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -56,8 +56,8 @@
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.StatusBarNotification;
+import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.R;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -87,14 +87,7 @@
private final String channelId = "channel";
private NotificationChannel channel =
new NotificationChannel(channelId, "test", NotificationManager.IMPORTANCE_DEFAULT);
- private final String channelIdLong =
- "give_a_developer_a_string_argument_and_who_knows_what_they_will_pass_in_there";
private final String groupId = "group";
- private final String groupIdOverride = "other_group";
- private final String groupIdLong =
- "0|com.foo.bar|g:content://com.foo.bar.ui/account%3A-0000000/account/";
- private NotificationChannel channelLongId =
- new NotificationChannel(channelIdLong, "long", NotificationManager.IMPORTANCE_DEFAULT);
private NotificationChannel defaultChannel =
new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "test",
NotificationManager.IMPORTANCE_UNSPECIFIED);
@@ -408,73 +401,27 @@
}
@Test
- public void testLogmakerShortChannel() {
+ public void testLogMaker() {
+ long timestamp = 1000L;
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
false /* lights */, false /* defaultLights */, null /* group */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- final LogMaker logMaker = record.getLogMaker();
+ final LogMaker logMaker = record.getLogMaker(timestamp);
+
+ assertNull(logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX));
assertEquals(channelId,
(String) logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID));
assertEquals(channel.getImportance(),
logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE));
- }
-
- @Test
- public void testLogmakerLongChannel() {
- StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
- true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
- false /* lights */, false /*defaultLights */, null /* group */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channelLongId);
- final String loggedId = (String)
- record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID);
- assertEquals(channelIdLong.substring(0,10), loggedId.substring(0, 10));
- }
-
- @Test
- public void testLogmakerNoGroup() {
- StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
- true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
- false /* lights */, false /*defaultLights */, null /* group */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- assertNull(record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
- }
-
- @Test
- public void testLogmakerShortGroup() {
- StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
- true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
- false /* lights */, false /* defaultLights */, groupId /* group */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- assertEquals(groupId,
- record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
- }
-
- @Test
- public void testLogmakerLongGroup() {
- StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
- true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
- false /* lights */, false /* defaultLights */, groupIdLong /* group */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- final String loggedId = (String)
- record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID);
- assertEquals(groupIdLong.substring(0,10), loggedId.substring(0, 10));
- }
-
- @Test
- public void testLogmakerOverrideGroup() {
- StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
- true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
- false /* lights */, false /* defaultLights */, groupId /* group */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- assertEquals(groupId,
- record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
- record.setOverrideGroupKey(groupIdOverride);
- assertEquals(groupIdOverride,
- record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
- record.setOverrideGroupKey(null);
- assertEquals(groupId,
- record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+ assertEquals(record.getLifespanMs(timestamp),
+ (int) logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS));
+ assertEquals(record.getFreshnessMs(timestamp),
+ (int) logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS));
+ assertEquals(record.getExposureMs(timestamp),
+ (int) logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS));
+ assertEquals(record.getInterruptionMs(timestamp),
+ (int) logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_INTERRUPTION_MILLIS));
}
@Test
@@ -845,4 +792,52 @@
assertFalse(record.isNewEnoughForAlerting(record.mUpdateTimeMs + (1000 * 60 * 60)));
}
+
+ @Test
+ public void testIgnoreImportanceAdjustmentsForOemLockedChannels() {
+ NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+ channel.setImportanceLockedByOEM(true);
+
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_LOW);
+ Adjustment adjustment = new Adjustment(
+ PKG_O, record.getKey(), bundle, "", record.getUserId());
+
+ record.addAdjustment(adjustment);
+ record.applyAdjustments();
+ record.calculateImportance();
+
+ assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+ }
+
+ @Test
+ public void testApplyImportanceAdjustmentsForNonOemLockedChannels() {
+ NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+ channel.setImportanceLockedByOEM(false);
+
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_LOW);
+ Adjustment adjustment = new Adjustment(
+ PKG_O, record.getKey(), bundle, "", record.getUserId());
+
+ record.addAdjustment(adjustment);
+ record.applyAdjustments();
+ record.calculateImportance();
+
+ assertEquals(IMPORTANCE_LOW, record.getImportance());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 0b73481..0fcfea7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2187,4 +2187,121 @@
assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_APP_OVERLAY,
mHelper.getAppLockedFields(PKG_O, UID_O));
}
+
+ @Test
+ public void testLockChannelsForOEM_emptyList() {
+ mHelper.lockChannelsForOEM(null);
+ mHelper.lockChannelsForOEM(new String[0]);
+ // no exception
+ }
+
+ @Test
+ public void testLockChannelsForOEM_appWide() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+ // different uids, same package
+ mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+ mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
+ mHelper.createNotificationChannel(PKG_O, 30, c, true, true);
+
+ mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+ .isImportanceLockedByOEM());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
+ .isImportanceLockedByOEM());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
+ .isImportanceLockedByOEM());
+ }
+
+ @Test
+ public void testLockChannelsForOEM_onlyGivenPkg() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, 30, b, false, false);
+
+ mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+ .isImportanceLockedByOEM());
+ assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 30, b.getId(), false)
+ .isImportanceLockedByOEM());
+ }
+
+ @Test
+ public void testLockChannelsForOEM_channelSpecific() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+ // different uids, same package
+ mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+ mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
+ mHelper.createNotificationChannel(PKG_O, 30, c, true, true);
+
+ mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"});
+
+ assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+ .isImportanceLockedByOEM());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
+ .isImportanceLockedByOEM());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
+ .isImportanceLockedByOEM());
+ }
+
+ @Test
+ public void testLockChannelsForOEM_channelDoesNotExistYet_appWide() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+
+ mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+ .isImportanceLockedByOEM());
+
+ mHelper.createNotificationChannel(PKG_O, 3, b, true, false);
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
+ .isImportanceLockedByOEM());
+ }
+
+ @Test
+ public void testLockChannelsForOEM_channelDoesNotExistYet_channelSpecific() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+
+ mHelper.lockChannelsForOEM(new String[] {PKG_O + ":a", PKG_O + ":b"});
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+ .isImportanceLockedByOEM());
+
+ mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false);
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+ .isImportanceLockedByOEM());
+ }
+
+ @Test
+ public void testUpdateNotificationChannel_oemLockedImportance() {
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+
+ mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+ NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE);
+ update.setAllowAppOverlay(false);
+
+ mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
+
+ assertEquals(IMPORTANCE_HIGH,
+ mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
+ assertEquals(false,
+ mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canOverlayApps());
+
+ mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
+
+ assertEquals(IMPORTANCE_HIGH,
+ mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
+ }
}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 0ccfb19..77cd9d8 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -108,6 +108,10 @@
}
// The app should not be available for rollback.
+ // TODO: See if there is a way to remove this race condition
+ // between when the app is uninstalled and when the previously
+ // available rollback, if any, is removed.
+ Thread.sleep(1000);
assertNull(rm.getAvailableRollback(TEST_APP_A));
assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
@@ -125,6 +129,10 @@
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
// The app should now be available for rollback.
+ // TODO: See if there is a way to remove this race condition
+ // between when the app is installed and when the rollback
+ // is made available.
+ Thread.sleep(1000);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
@@ -182,6 +190,8 @@
RollbackManager rm = RollbackTestUtils.getRollbackManager();
+ // TODO: Test this with multi-package rollback, not just single
+ // package rollback.
// Prep installation of TEST_APP_A
RollbackTestUtils.uninstall(TEST_APP_A);
RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
@@ -189,6 +199,10 @@
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
// The app should now be available for rollback.
+ // TODO: See if there is a way to remove this race condition
+ // between when the app is installed and when the rollback
+ // is made available.
+ Thread.sleep(1000);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
@@ -263,6 +277,10 @@
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
// The app should now be available for rollback.
+ // TODO: See if there is a way to remove this race condition
+ // between when the app is installed and when the rollback
+ // is made available.
+ Thread.sleep(1000);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
@@ -405,6 +423,10 @@
// Both test apps should now be available for rollback, and the
// targetPackage returned for rollback should be correct.
+ // TODO: See if there is a way to remove this race condition
+ // between when the app is installed and when the rollback
+ // is made available.
+ Thread.sleep(1000);
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
@@ -491,7 +513,7 @@
* TODO: Stop ignoring this test once support for multi-package rollback
* is implemented.
*/
- @Ignore @Test
+ @Test
public void testMultiPackage() throws Exception {
try {
RollbackTestUtils.adoptShellPermissionIdentity(
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 6631fa8..f931ad2 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -223,7 +223,8 @@
private MacAddress mDeviceAddress = MAC_ANY_ADDRESS;
private String mNetworkName = "";
private String mPassphrase = "";
- private int mGroupOwnerBand = GROUP_OWNER_BAND_AUTO;
+ private int mGroupOperatingBand = GROUP_OWNER_BAND_AUTO;
+ private int mGroupOperatingFrequency = GROUP_OWNER_BAND_AUTO;
private int mNetId = WifiP2pGroup.TEMPORARY_NET_ID;
/**
@@ -285,22 +286,84 @@
}
/**
- * Specify the band to use for creating the group. This method only applies when
- * creating a group as Group Owner using {@link WifiP2pManager#createGroup}.
- * The band should be {@link #GROUP_OWNER_BAND_2GHZ} or {@link #GROUP_OWNER_BAND_5GHZ},
- * or allow the system to pick the band by specifying {@link #GROUP_OWNER_BAND_AUTO}.
+ * Specify the band to use for creating the group or joining the group. The band should
+ * be {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ} or
+ * {@link #GROUP_OWNER_BAND_AUTO}.
+ * <p>
+ * When creating a group as Group Owner using {@link
+ * WifiP2pManager#createGroup(WifiP2pManager.Channel,
+ * WifiP2pConfig, WifiP2pManager.ActionListener)},
+ * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to pick the operating
+ * frequency from all supported bands.
+ * Specifying {@link #GROUP_OWNER_BAND_2GHZ} or {@link #GROUP_OWNER_BAND_5GHZ}
+ * only allows the system to pick the operating frequency in the specified band.
* If the Group Owner cannot create a group in the specified band, the operation will fail.
* <p>
+ * When joining a group as Group Client using {@link
+ * WifiP2pManager#connect(WifiP2pManager.Channel, WifiP2pConfig,
+ * WifiP2pManager.ActionListener)},
+ * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to scan all supported
+ * frequencies to find the desired group. Specifying {@link #GROUP_OWNER_BAND_2GHZ} or
+ * {@link #GROUP_OWNER_BAND_5GHZ} only allows the system to scan the specified band.
+ * <p>
+ * {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are
+ * mutually exclusive. Setting operating band and frequency both is invalid.
+ * <p>
* Optional. {@link #GROUP_OWNER_BAND_AUTO} by default.
*
- * @param band the required band of group owner.
+ * @param band the operating band of the group.
* This should be one of {@link #GROUP_OWNER_BAND_AUTO},
* {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ}.
* @return The builder to facilitate chaining
* {@code builder.setXXX(..).setXXX(..)}.
*/
- public Builder setGroupOwnerBand(int band) {
- mGroupOwnerBand = band;
+ public Builder setGroupOperatingBand(@GroupOwnerBandType int band) {
+ switch (band) {
+ case GROUP_OWNER_BAND_AUTO:
+ case GROUP_OWNER_BAND_2GHZ:
+ case GROUP_OWNER_BAND_5GHZ:
+ mGroupOperatingBand = band;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid constant for the group operating band!");
+ }
+ return this;
+ }
+
+ /**
+ * Specify the frequency to use for creating the group or joining the group.
+ * <p>
+ * When creating a group as Group Owner using {@link WifiP2pManager#createGroup(
+ * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)},
+ * specifying a frequency only allows the system to pick the specified frequency.
+ * If the Group Owner cannot create a group at the specified frequency,
+ * the operation will fail.
+ * When not specifying a frequency, it allows the system to pick operating frequency
+ * from all supported bands.
+ * <p>
+ * When joining a group as Group Client using {@link WifiP2pManager#connect(
+ * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)},
+ * specifying a frequency only allows the system to scan the specified frequency.
+ * If the frequency is not supported or invalid, the operation will fail.
+ * When not specifying a frequency, it allows the system to scan all supported
+ * frequencies to find the desired group.
+ * <p>
+ * {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are
+ * mutually exclusive. Setting operating band and frequency both is invalid.
+ * <p>
+ * Optional. 0 by default.
+ *
+ * @param frequency the operating frequency of the group.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setGroupOperatingFrequency(int frequency) {
+ if (frequency < 0) {
+ throw new IllegalArgumentException(
+ "Invalid group operating frequency!");
+ }
+ mGroupOperatingFrequency = frequency;
return this;
}
@@ -337,11 +400,21 @@
"passphrase must be non-empty.");
}
+ if (mGroupOperatingFrequency > 0 && mGroupOperatingBand > 0) {
+ throw new IllegalStateException(
+ "Preferred frequency and band are mutually exclusive.");
+ }
+
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = mDeviceAddress.toString();
config.networkName = mNetworkName;
config.passphrase = mPassphrase;
- config.groupOwnerBand = mGroupOwnerBand;
+ config.groupOwnerBand = GROUP_OWNER_BAND_AUTO;
+ if (mGroupOperatingFrequency > 0) {
+ config.groupOwnerBand = mGroupOperatingFrequency;
+ } else if (mGroupOperatingBand > 0) {
+ config.groupOwnerBand = mGroupOperatingBand;
+ }
config.netId = mNetId;
return config;
}