Merge "Set the global priority session immediately after its flag is set" into oc-mr1-dev
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index d6e3691..556acdc 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -15,8 +15,11 @@
*/
package android.app;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.NotificationManager.Importance;
+import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.net.Uri;
@@ -26,6 +29,8 @@
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import com.android.internal.util.Preconditions;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
@@ -565,14 +570,35 @@
/**
* @hide
*/
+ public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
+ populateFromXml(parser, true, context);
+ }
+
+ /**
+ * @hide
+ */
@SystemApi
public void populateFromXml(XmlPullParser parser) {
+ populateFromXml(parser, false, null);
+ }
+
+ /**
+ * If {@param forRestore} is true, {@param Context} MUST be non-null.
+ */
+ private void populateFromXml(XmlPullParser parser, boolean forRestore,
+ @Nullable Context context) {
+ Preconditions.checkArgument(!forRestore || context != null,
+ "forRestore is true but got null context");
+
// Name, id, and importance are set in the constructor.
setDescription(parser.getAttributeValue(null, ATT_DESC));
setBypassDnd(Notification.PRIORITY_DEFAULT
!= safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
- setSound(safeUri(parser, ATT_SOUND), safeAudioAttributes(parser));
+
+ Uri sound = safeUri(parser, ATT_SOUND);
+ setSound(forRestore ? restoreSoundUri(context, sound) : sound, safeAudioAttributes(parser));
+
enableLights(safeBool(parser, ATT_LIGHTS, false));
setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
@@ -584,11 +610,62 @@
setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
}
+ @Nullable
+ private Uri restoreSoundUri(Context context, @Nullable Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ ContentResolver contentResolver = context.getContentResolver();
+ // There are backups out there with uncanonical uris (because we fixed this after
+ // shipping). If uncanonical uris are given to MediaProvider.uncanonicalize it won't
+ // verify the uri against device storage and we'll possibly end up with a broken uri.
+ // We then canonicalize the uri to uncanonicalize it back, which means we properly check
+ // the uri and in the case of not having the resource we end up with the default - better
+ // than broken. As a side effect we'll canonicalize already canonicalized uris, this is fine
+ // according to the docs because canonicalize method has to handle canonical uris as well.
+ Uri canonicalizedUri = contentResolver.canonicalize(uri);
+ if (canonicalizedUri == null) {
+ // We got a null because the uri in the backup does not exist here, so we return default
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
+ }
+ return contentResolver.uncanonicalize(canonicalizedUri);
+ }
+
/**
* @hide
*/
@SystemApi
public void writeXml(XmlSerializer out) throws IOException {
+ writeXml(out, false, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void writeXmlForBackup(XmlSerializer out, Context context) throws IOException {
+ writeXml(out, true, context);
+ }
+
+ private Uri getSoundForBackup(Context context) {
+ Uri sound = getSound();
+ if (sound == null) {
+ return null;
+ }
+ Uri canonicalSound = context.getContentResolver().canonicalize(sound);
+ if (canonicalSound == null) {
+ // The content provider does not support canonical uris so we backup the default
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
+ }
+ return canonicalSound;
+ }
+
+ /**
+ * If {@param forBackup} is true, {@param Context} MUST be non-null.
+ */
+ private void writeXml(XmlSerializer out, boolean forBackup, @Nullable Context context)
+ throws IOException {
+ Preconditions.checkArgument(!forBackup || context != null,
+ "forBackup is true but got null context");
out.startTag(null, TAG_CHANNEL);
out.attribute(null, ATT_ID, getId());
if (getName() != null) {
@@ -609,8 +686,9 @@
out.attribute(null, ATT_VISIBILITY,
Integer.toString(getLockscreenVisibility()));
}
- if (getSound() != null) {
- out.attribute(null, ATT_SOUND, getSound().toString());
+ Uri sound = forBackup ? getSoundForBackup(context) : getSound();
+ if (sound != null) {
+ out.attribute(null, ATT_SOUND, sound.toString());
}
if (getAudioAttributes() != null) {
out.attribute(null, ATT_USAGE, Integer.toString(getAudioAttributes().getUsage()));
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index ee8eed1..3d2e1d1 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -206,8 +206,7 @@
try {
mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
.getAudioAttributes())
- .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
- AudioAttributes.FLAG_BYPASS_MUTE)
+ .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
.build());
mRingtone.play();
} catch (Throwable e) {
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 1521e7e..14e9904 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -365,6 +365,81 @@
* <p><b>Note:</b> The autofill service could also whitelist well-known browser apps and skip the
* verifications above, as long as the service can verify the authenticity of the browser app by
* checking its signing certificate.
+ *
+ * <a name="MultipleStepsSave"></a>
+ * <h3>Saving when data is split in multiple screens</h3>
+ *
+ * Apps often split the user data in multiple screens in the same activity, specially in
+ * activities used to create a new user account. For example, the first screen asks for a username,
+ * and if the username is available, it moves to a second screen, which asks for a password.
+ *
+ * <p>It's tricky to handle save for autofill in these situations, because the autofill service must
+ * wait until the user enters both fields before the autofill save UI can be shown. But it can be
+ * done by following the steps below:
+ *
+ * <ol>
+ * <li>In the first
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * adds a {@link FillResponse.Builder#setClientState(android.os.Bundle) client state bundle} in
+ * the response, containing the autofill ids of the partial fields present in the screen.
+ * <li>In the second
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * retrieves the {@link FillRequest#getClientState() client state bundle}, gets the autofill ids
+ * set in the previous request from the client state, and adds these ids and the
+ * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} to the {@link SaveInfo} used in the second
+ * response.
+ * <li>In the {@link #onSaveRequest(SaveRequest, SaveCallback) save request}, the service uses the
+ * proper {@link FillContext fill contexts} to get the value of each field (there is one fill
+ * context per fill request).
+ * </ol>
+ *
+ * <p>For example, in an app that uses 2 steps for the username and password fields, the workflow
+ * would be:
+ * <pre class="prettyprint">
+ * // On first fill request
+ * AutofillId usernameId = // parse from AssistStructure;
+ * Bundle clientState = new Bundle();
+ * clientState.putParcelable("usernameId", usernameId);
+ * fillCallback.onSuccess(
+ * new FillResponse.Builder()
+ * .setClientState(clientState)
+ * .setSaveInfo(new SaveInfo
+ * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME, new AutofillId[] {usernameId})
+ * .build())
+ * .build());
+ *
+ * // On second fill request
+ * Bundle clientState = fillRequest.getClientState();
+ * AutofillId usernameId = clientState.getParcelable("usernameId");
+ * AutofillId passwordId = // parse from AssistStructure
+ * clientState.putParcelable("passwordId", passwordId);
+ * fillCallback.onSuccess(
+ * new FillResponse.Builder()
+ * .setClientState(clientState)
+ * .setSaveInfo(new SaveInfo
+ * .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ * new AutofillId[] {usernameId, passwordId})
+ * .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+ * .build())
+ * .build());
+ *
+ * // On save request
+ * Bundle clientState = saveRequest.getClientState();
+ * AutofillId usernameId = clientState.getParcelable("usernameId");
+ * AutofillId passwordId = clientState.getParcelable("passwordId");
+ * List<FillContext> fillContexts = saveRequest.getFillContexts();
+ *
+ * FillContext usernameContext = fillContexts.get(0);
+ * ViewNode usernameNode = findNodeByAutofillId(usernameContext.getStructure(), usernameId);
+ * AutofillValue username = usernameNode.getAutofillValue().getTextValue().toString();
+ *
+ * FillContext passwordContext = fillContexts.get(1);
+ * ViewNode passwordNode = findNodeByAutofillId(passwordContext.getStructure(), passwordId);
+ * AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
+ *
+ * save(username, password);
+ *
+ * </pre>
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index 0f21c5c..d785117 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -329,8 +329,6 @@
final long oldParentId = oldInfo.getParentNodeId();
if (info.getParentNodeId() != oldParentId) {
clearSubTreeLocked(windowId, oldParentId);
- } else {
- oldInfo.recycle();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index dc626fb..851b78c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -84,9 +84,6 @@
case DOZE_REQUEST_PULSE:
pulseWhileDozing(mMachine.getPulseReason());
break;
- case DOZE_PULSE_DONE:
- mHost.abortPulsing();
- break;
case INITIALIZED:
mHost.startDozing();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
index b835909..5ec3dff 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -23,7 +23,7 @@
*/
public class DelayedWakeLock implements WakeLock {
- private static final long RELEASE_DELAY_MS = 100;
+ private static final long RELEASE_DELAY_MS = 120;
private final Handler mHandler;
private final WakeLock mInner;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index ac85e6b..16995e5 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -33,6 +33,7 @@
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -517,14 +518,27 @@
} catch (PackageManager.NameNotFoundException e) {
}
}
- if (localForegroundNoti.getSmallIcon() == null
- || nm.getNotificationChannel(localPackageName, appUid,
+ if (nm.getNotificationChannel(localPackageName, appUid,
localForegroundNoti.getChannelId()) == null) {
+ int targetSdkVersion = Build.VERSION_CODES.O_MR1;
+ try {
+ final ApplicationInfo applicationInfo =
+ ams.mContext.getPackageManager().getApplicationInfoAsUser(
+ appInfo.packageName, 0, userId);
+ targetSdkVersion = applicationInfo.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ if (targetSdkVersion >= Build.VERSION_CODES.O_MR1) {
+ throw new RuntimeException(
+ "invalid channel for service notification: "
+ + foregroundNoti);
+ }
+ }
+ if (localForegroundNoti.getSmallIcon() == null) {
// Notifications whose icon is 0 are defined to not show
// a notification, silently ignoring it. We don't want to
// just ignore it, we want to prevent the service from
// being foreground.
- // Also every notification needs a channel.
throw new RuntimeException("invalid service notification: "
+ foregroundNoti);
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 6506cf7..4943173 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -184,11 +184,15 @@
}
}
+ private static final int FLAGS_FOR_SILENCE_OVERRIDE =
+ AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
+ AudioAttributes.FLAG_BYPASS_MUTE;
+
private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
- if ((apc.getAudioAttributes().getAllFlags() &
- AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0 &&
+ if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE)
+ == FLAGS_FOR_SILENCE_OVERRIDE &&
apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
apc.getClientPid(), apc.getClientUid()) ==
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index a985b4f..0993f42 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1315,6 +1315,7 @@
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
}
+ mUpstreamNetworkMonitor.setCurrentUpstream((ns != null) ? ns.network : null);
setUpstreamNetwork(ns);
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index c5f7528..b35ed75 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -95,7 +95,10 @@
private NetworkCallback mDefaultNetworkCallback;
private NetworkCallback mMobileNetworkCallback;
private boolean mDunRequired;
- private Network mCurrentDefault;
+ // The current system default network (not really used yet).
+ private Network mDefaultInternetNetwork;
+ // The current upstream network used for tethering.
+ private Network mTetheringUpstreamNetwork;
public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
mContext = ctx;
@@ -130,10 +133,12 @@
releaseCallback(mDefaultNetworkCallback);
mDefaultNetworkCallback = null;
+ mDefaultInternetNetwork = null;
releaseCallback(mListenAllCallback);
mListenAllCallback = null;
+ mTetheringUpstreamNetwork = null;
mNetworkMap.clear();
}
@@ -207,7 +212,7 @@
break;
default:
/* If we've found an active upstream connection that's not DUN/HIPRI
- * we should stop any outstanding DUN/HIPRI start requests.
+ * we should stop any outstanding DUN/HIPRI requests.
*
* If we found NONE we don't want to do this as we want any previous
* requests to keep trying to bring up something we can use.
@@ -219,6 +224,10 @@
return typeStatePair.ns;
}
+ public void setCurrentUpstream(Network upstream) {
+ mTetheringUpstreamNetwork = upstream;
+ }
+
public Set<IpPrefix> getLocalPrefixes() {
return (Set<IpPrefix>) mLocalPrefixes.clone();
}
@@ -250,7 +259,7 @@
// These request*() calls can be deleted post oag/339444.
return;
}
- mCurrentDefault = network;
+ mDefaultInternetNetwork = network;
break;
case CALLBACK_MOBILE_REQUEST:
@@ -302,6 +311,13 @@
network, newNc));
}
+ // Log changes in upstream network signal strength, if available.
+ if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) {
+ final int newSignal = newNc.getSignalStrength();
+ final String prevSignal = getSignalStrength(prev.networkCapabilities);
+ mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal);
+ }
+
mNetworkMap.put(network, new NetworkState(
null, prev.linkProperties, newNc, network, null, null));
// TODO: If sufficient information is available to select a more
@@ -330,9 +346,21 @@
notifyTarget(EVENT_ON_LINKPROPERTIES, network);
}
+ private void handleSuspended(int callbackType, Network network) {
+ if (callbackType != CALLBACK_LISTEN_ALL) return;
+ if (!network.equals(mTetheringUpstreamNetwork)) return;
+ mLog.log("SUSPENDED current upstream: " + network);
+ }
+
+ private void handleResumed(int callbackType, Network network) {
+ if (callbackType != CALLBACK_LISTEN_ALL) return;
+ if (!network.equals(mTetheringUpstreamNetwork)) return;
+ mLog.log("RESUMED current upstream: " + network);
+ }
+
private void handleLost(int callbackType, Network network) {
if (callbackType == CALLBACK_TRACK_DEFAULT) {
- mCurrentDefault = null;
+ mDefaultInternetNetwork = null;
// Receiving onLost() for a default network does not necessarily
// mean the network is gone. We wait for a separate notification
// on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
@@ -401,8 +429,15 @@
recomputeLocalPrefixes();
}
- // TODO: Handle onNetworkSuspended();
- // TODO: Handle onNetworkResumed();
+ @Override
+ public void onNetworkSuspended(Network network) {
+ handleSuspended(mCallbackType, network);
+ }
+
+ @Override
+ public void onNetworkResumed(Network network) {
+ handleResumed(mCallbackType, network);
+ }
@Override
public void onLost(Network network) {
@@ -467,4 +502,9 @@
return prefixSet;
}
+
+ private static String getSignalStrength(NetworkCapabilities nc) {
+ if (nc == null || !nc.hasSignalStrength()) return "unknown";
+ return Integer.toString(nc.getSignalStrength());
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4e46535..842ee91 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -281,6 +281,7 @@
private WindowManagerInternal mWindowManagerInternal;
private AlarmManager mAlarmManager;
private ICompanionDeviceManager mCompanionManager;
+ private AccessibilityManager mAccessibilityManager;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -1190,6 +1191,12 @@
mUsageStats = us;
}
+ @VisibleForTesting
+ void setAccessibilityManager(AccessibilityManager am) {
+ mAccessibilityManager = am;
+ }
+
+
// TODO: All tests should use this init instead of the one-off setters above.
@VisibleForTesting
void init(Looper looper, IPackageManager packageManager,
@@ -1204,6 +1211,8 @@
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE);
+ mAccessibilityManager =
+ (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
mAm = ActivityManager.getService();
mPackageManager = packageManager;
mPackageManagerClient = packageManagerClient;
@@ -4015,13 +4024,16 @@
// These are set inside the conditional if the notification is allowed to make noise.
boolean hasValidVibrate = false;
boolean hasValidSound = false;
+ boolean sentAccessibilityEvent = false;
+ // If the notification will appear in the status bar, it should send an accessibility
+ // event
+ if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
+ sendAccessibilityEvent(notification, record.sbn.getPackageName());
+ sentAccessibilityEvent = true;
+ }
if (aboveThreshold && isNotificationForCurrentUser(record)) {
- // If the notification will appear in the status bar, it should send an accessibility
- // event
- if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
- sendAccessibilityEvent(notification, record.sbn.getPackageName());
- }
+
if (mSystemReady && mAudioManager != null) {
Uri soundUri = record.getSound();
hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
@@ -4039,6 +4051,10 @@
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
+ if (!sentAccessibilityEvent) {
+ sendAccessibilityEvent(notification, record.sbn.getPackageName());
+ sentAccessibilityEvent = true;
+ }
if (DBG) Slog.v(TAG, "Interrupting!");
if (hasValidSound) {
mSoundNotificationKey = key;
@@ -4556,8 +4572,7 @@
}
void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
- AccessibilityManager manager = AccessibilityManager.getInstance(getContext());
- if (!manager.isEnabled()) {
+ if (!mAccessibilityManager.isEnabled()) {
return;
}
@@ -4571,7 +4586,7 @@
event.getText().add(tickerText);
}
- manager.sendAccessibilityEvent(event);
+ mAccessibilityManager.sendAccessibilityEvent(event);
}
/**
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 9db4584..b8e2092 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -227,7 +227,11 @@
if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
NotificationChannel channel = new NotificationChannel(id,
channelName, channelImportance);
- channel.populateFromXml(parser);
+ if (forRestore) {
+ channel.populateFromXmlForRestore(parser, mContext);
+ } else {
+ channel.populateFromXml(parser);
+ }
r.channels.put(id, channel);
}
}
@@ -390,7 +394,11 @@
}
for (NotificationChannel channel : r.channels.values()) {
- if (!forBackup || (forBackup && !channel.isDeleted())) {
+ if (forBackup) {
+ if (!channel.isDeleted()) {
+ channel.writeXmlForBackup(out, mContext);
+ }
+ } else {
channel.writeXml(out);
}
}
diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java
index 343d237..bbd3d13 100644
--- a/services/net/java/android/net/util/SharedLog.java
+++ b/services/net/java/android/net/util/SharedLog.java
@@ -106,6 +106,10 @@
record(Category.NONE, msg);
}
+ public void logf(String fmt, Object... args) {
+ log(String.format(fmt, args));
+ }
+
public void mark(String msg) {
record(Category.MARK, msg);
}
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 9fa1d68..0b4d61f 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -19,6 +19,7 @@
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@@ -57,7 +58,13 @@
import android.service.notification.StatusBarNotification;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+import com.android.internal.util.IntPair;
import com.android.server.lights.Light;
import org.junit.Before;
@@ -67,6 +74,8 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -80,6 +89,8 @@
NotificationManagerService.WorkerHandler mHandler;
@Mock
NotificationUsageStats mUsageStats;
+ @Mock
+ IAccessibilityManager mAccessibilityService;
private NotificationManagerService mService;
private String mPkg = "com.android.server.notification";
@@ -111,17 +122,25 @@
private static final int MAX_VIBRATION_DELAY = 1000;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-
when(mUsageStats.isAlertRateLimited(any())).thenReturn(false);
- mService = new NotificationManagerService(getContext());
+ long serviceReturnValue = IntPair.of(
+ AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED,
+ AccessibilityEvent.TYPES_ALL_MASK);
+ when(mAccessibilityService.addClient(any(), anyInt())).thenReturn(serviceReturnValue);
+ AccessibilityManager accessibilityManager =
+ new AccessibilityManager(Handler.getMain(), mAccessibilityService, 0);
+ verify(mAccessibilityService).addClient(any(IAccessibilityManagerClient.class), anyInt());
+ assertTrue(accessibilityManager.isEnabled());
+
+ mService = spy(new NotificationManagerService(getContext()));
mService.setAudioManager(mAudioManager);
mService.setVibrator(mVibrator);
mService.setSystemReady(true);
@@ -130,6 +149,7 @@
mService.setScreenOn(false);
mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
mService.setUsageStats(mUsageStats);
+ mService.setAccessibilityManager(accessibilityManager);
}
//
@@ -381,6 +401,7 @@
verifyBeepLooped();
verifyNeverVibrate();
+ verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
}
@Test
@@ -435,6 +456,7 @@
r.isUpdate = true;
mService.buzzBeepBlinkLocked(r);
verifyBeepLooped();
+ verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
}
@Test
@@ -450,6 +472,7 @@
// update should not beep
mService.buzzBeepBlinkLocked(s);
verifyNeverBeep();
+ verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
}
@Test
@@ -547,7 +570,7 @@
mService.mInCall = true;
mService.buzzBeepBlinkLocked(r);
- //verify(mService, times(1)).playInCallNotification();
+ verify(mService, times(1)).playInCallNotification();
verifyNeverBeep(); // doesn't play normal beep
}
@@ -842,7 +865,6 @@
mService.addNotification(r);
mService.buzzBeepBlinkLocked(r);
-
verifyNeverBeep();
}
@@ -870,7 +892,6 @@
summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
mService.buzzBeepBlinkLocked(summary);
-
verify(mUsageStats, never()).isAlertRateLimited(any());
}
@@ -889,6 +910,30 @@
verifyNeverBeep();
}
+ @Test
+ public void testA11yMinInitialPost() throws Exception {
+ NotificationRecord r = getQuietNotification();
+ r.setImportance(IMPORTANCE_MIN, "");
+ mService.buzzBeepBlinkLocked(r);
+ verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
+ public void testA11yQuietInitialPost() throws Exception {
+ NotificationRecord r = getQuietNotification();
+ mService.buzzBeepBlinkLocked(r);
+ verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
+ public void testA11yQuietUpdate() throws Exception {
+ NotificationRecord r = getQuietNotification();
+ mService.buzzBeepBlinkLocked(r);
+ r.isUpdate = true;
+ mService.buzzBeepBlinkLocked(r);
+ verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
+ }
+
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
private final int mRepeatIndex;
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 61d999a..c382e53 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -25,25 +25,13 @@
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import com.android.internal.util.FastXmlSerializer;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
-
import android.app.Notification;
-import android.app.NotificationChannelGroup;
-import android.content.Context;
import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -52,14 +40,28 @@
import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
+import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContentResolver;
import android.util.ArrayMap;
import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -76,6 +78,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -95,10 +98,17 @@
private static final int UID2 = 1111;
private static final UserHandle USER2 = UserHandle.of(10);
private static final String TEST_CHANNEL_ID = "test_channel_id";
+ private static final String TEST_AUTHORITY = "test";
+ private static final Uri SOUND_URI =
+ Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10");
+ private static final Uri CANONICAL_SOUND_URI =
+ Uri.parse("content://" + TEST_AUTHORITY
+ + "/internal/audio/media/10?title=Test&canonical=1");
@Mock NotificationUsageStats mUsageStats;
@Mock RankingHandler mHandler;
@Mock PackageManager mPm;
+ @Mock IContentProvider mTestIContentProvider;
@Mock Context mContext;
private Notification mNotiGroupGSortA;
@@ -134,9 +144,22 @@
when(mContext.getPackageManager()).thenReturn(mPm);
when(mContext.getApplicationInfo()).thenReturn(legacy);
// most tests assume badging is enabled
- Secure.putIntForUser(getContext().getContentResolver(),
+ TestableContentResolver contentResolver = getContext().getContentResolver();
+ contentResolver.setFallbackToExisting(false);
+ Secure.putIntForUser(contentResolver,
Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID));
+ ContentProvider testContentProvider = mock(ContentProvider.class);
+ when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
+ contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
+
+ when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
+ .thenReturn(CANONICAL_SOUND_URI);
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(CANONICAL_SOUND_URI);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(SOUND_URI);
+
mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
new String[] {ImportanceExtractor.class.getName()});
@@ -214,9 +237,12 @@
}
private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore) throws Exception {
+ loadByteArrayXml(stream.toByteArray(), forRestore);
+ }
+
+ private void loadByteArrayXml(byte[] byteArray, boolean forRestore) throws Exception {
XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(stream.toByteArray())),
- null);
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
parser.nextTag();
mHelper.readXml(parser, forRestore);
}
@@ -364,7 +390,7 @@
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel2.setDescription("descriptions for all");
- channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel2.setSound(SOUND_URI, mAudioAttributes);
channel2.enableLights(true);
channel2.setBypassDnd(true);
channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
@@ -426,6 +452,109 @@
}
@Test
+ public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(SOUND_URI, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ // Testing that in restore we are given the canonical version
+ loadStreamXml(baos, true);
+ verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
+ }
+
+ @Test
+ public void testRestoreXml_withExistentCanonicalizedSoundUri() throws Exception {
+ Uri localUri = Uri.parse("content://" + TEST_AUTHORITY + "/local/url");
+ Uri canonicalBasedOnLocal = localUri.buildUpon()
+ .appendQueryParameter("title", "Test")
+ .appendQueryParameter("canonical", "1")
+ .build();
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(canonicalBasedOnLocal);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(localUri);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
+ .thenReturn(localUri);
+
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(SOUND_URI, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ loadStreamXml(baos, true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false);
+ assertEquals(localUri, actualChannel.getSound());
+ }
+
+ @Test
+ public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
+ Thread.sleep(3000);
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(null);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(null);
+
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(SOUND_URI, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ loadStreamXml(baos, true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+ }
+
+
+ /**
+ * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
+ * handle its restore properly.
+ */
+ @Test
+ public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
+ // Not a local uncanonicalized uri, simulating that it fails to exist locally
+ when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
+ String id = "id";
+ String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
+ + "<package name=\"com.android.server.notification\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "sound=\"" + SOUND_URI + "\" "
+ + "usage=\"6\" content_type=\"0\" flags=\"1\" show_badge=\"true\" />\n"
+ + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ loadByteArrayXml(backupWithUncanonicalizedSoundUri.getBytes(), true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG, UID, id, false);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+ }
+
+ @Test
+ public void testBackupRestoreXml_withNullSoundUri() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(null, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ loadStreamXml(baos, true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false);
+ assertEquals(null, actualChannel.getSound());
+ }
+
+ @Test
public void testChannelXml_backup() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
index 02f645a..c8dc9ff 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
@@ -77,7 +77,6 @@
mAccessibilityCache.clear();
AccessibilityInteractionClient.getInstance().clearCache();
assertEquals(0, numA11yWinInfosInUse.get());
- assertEquals(0, numA11yNodeInfosInUse.get());
}
@Test
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index ac196b5..5523f71 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -288,7 +288,7 @@
return;
}
- List<Uri> tempFiles = intent.getParcelableExtra(VendorUtils.EXTRA_TEMP_LIST);
+ List<Uri> tempFiles = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_LIST);
if (tempFiles == null) {
return;
}
@@ -310,7 +310,7 @@
return;
}
int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0);
- List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST);
+ List<Uri> pausedList = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_PAUSED_LIST);
if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
Log.i(LOG_TAG, "No temp files actually requested. Ending.");
@@ -493,9 +493,14 @@
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Package manager couldn't find " + context.getPackageName());
}
+ if (appInfo.metaData == null) {
+ throw new RuntimeException("App must declare the file provider authority as metadata " +
+ "in the manifest.");
+ }
String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY);
if (authority == null) {
- throw new RuntimeException("Must declare the file provider authority as meta data");
+ throw new RuntimeException("App must declare the file provider authority as metadata " +
+ "in the manifest.");
}
return authority;
}
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 8210403..163250d 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -296,6 +296,7 @@
Notification n = new Notification.Builder(NotificationTestList.this, "min")
.setSmallIcon(R.drawable.icon2)
.setContentTitle("Min priority")
+ .setTicker("Min priority")
.build();
mNM.notify("min", 7000, n);
}
@@ -306,6 +307,7 @@
Notification n = new Notification.Builder(NotificationTestList.this, "low")
.setSmallIcon(R.drawable.icon2)
.setContentTitle("Low priority")
+ .setTicker("Low priority")
.build();
mNM.notify("low", 7002, n);
}
@@ -326,6 +328,7 @@
Notification n = new Notification.Builder(NotificationTestList.this, "high")
.setSmallIcon(R.drawable.icon2)
.setContentTitle("High priority")
+ .setTicker("High priority")
.build();
mNM.notify("high", 7006, n);
}