diff options
18 files changed, 476 insertions, 62 deletions
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index d6e36914ac6c..556acdcfff81 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.provider.Settings; 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 @@ public final class NotificationChannel implements Parcelable { /** * @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 @@ public final class NotificationChannel implements Parcelable { 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 @@ public final class NotificationChannel implements Parcelable { 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 ee8eed1906f4..3d2e1d1f1d24 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -206,8 +206,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba 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 1521e7e656a1..14e9904133bd 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -365,6 +365,81 @@ import com.android.internal.os.SomeArgs; * <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 0f21c5c85f4b..d7851171cd67 100644 --- a/core/java/android/view/accessibility/AccessibilityCache.java +++ b/core/java/android/view/accessibility/AccessibilityCache.java @@ -329,8 +329,6 @@ public final class AccessibilityCache { 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 dc626fb4df65..851b78cfcd49 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 @@ public class DozeUi implements DozeMachine.Part { 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 b83590979ef7..5ec3dff0043c 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 @@ import android.os.Handler; */ 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 ac85e6b132bf..16995e50fdbf 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.PackageManager; 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 @@ final class ServiceRecord extends Binder { } 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 6506cf7fa189..494317334f43 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 @@ public final class PlaybackActivityMonitor } } + 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 a985b4fb10b8..0993f422d16e 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 @@ public class Tethering extends BaseNetworkObserver { 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 c5f752807cb7..b35ed75106b3 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 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { releaseCallback(mDefaultNetworkCallback); mDefaultNetworkCallback = null; + mDefaultInternetNetwork = null; releaseCallback(mListenAllCallback); mListenAllCallback = null; + mTetheringUpstreamNetwork = null; mNetworkMap.clear(); } @@ -207,7 +212,7 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { return typeStatePair.ns; } + public void setCurrentUpstream(Network upstream) { + mTetheringUpstreamNetwork = upstream; + } + public Set<IpPrefix> getLocalPrefixes() { return (Set<IpPrefix>) mLocalPrefixes.clone(); } @@ -250,7 +259,7 @@ public class UpstreamNetworkMonitor { // These request*() calls can be deleted post oag/339444. return; } - mCurrentDefault = network; + mDefaultInternetNetwork = network; break; case CALLBACK_MOBILE_REQUEST: @@ -302,6 +311,13 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { 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 4e465357c8e6..842ee9135e33 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 @@ public class NotificationManagerService extends SystemService { private WindowManagerInternal mWindowManagerInternal; private AlarmManager mAlarmManager; private ICompanionDeviceManager mCompanionManager; + private AccessibilityManager mAccessibilityManager; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; @@ -1190,6 +1191,12 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { // 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } void sendAccessibilityEvent(Notification notification, CharSequence packageName) { - AccessibilityManager manager = AccessibilityManager.getInstance(getContext()); - if (!manager.isEnabled()) { + if (!mAccessibilityManager.isEnabled()) { return; } @@ -4571,7 +4586,7 @@ public class NotificationManagerService extends SystemService { 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 9db458452b9c..b8e2092fd2fa 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 @@ public class RankingHelper implements RankingConfig { 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 @@ public class RankingHelper implements RankingConfig { } 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 343d237f8cd9..bbd3d13efbd6 100644 --- a/services/net/java/android/net/util/SharedLog.java +++ b/services/net/java/android/net/util/SharedLog.java @@ -106,6 +106,10 @@ public class SharedLog { 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 9fa1d68b8628..0b4d61fb783e 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_ALL; 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.provider.Settings; 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.ArgumentMatcher; 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 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { NotificationManagerService.WorkerHandler mHandler; @Mock NotificationUsageStats mUsageStats; + @Mock + IAccessibilityManager mAccessibilityService; private NotificationManagerService mService; private String mPkg = "com.android.server.notification"; @@ -111,17 +122,25 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { 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 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { mService.setScreenOn(false); mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN); mService.setUsageStats(mUsageStats); + mService.setAccessibilityManager(accessibilityManager); } // @@ -381,6 +401,7 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { verifyBeepLooped(); verifyNeverVibrate(); + verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); } @Test @@ -435,6 +456,7 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { r.isUpdate = true; mService.buzzBeepBlinkLocked(r); verifyBeepLooped(); + verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt()); } @Test @@ -450,6 +472,7 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { // update should not beep mService.buzzBeepBlinkLocked(s); verifyNeverBeep(); + verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); } @Test @@ -547,7 +570,7 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { 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 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { mService.addNotification(r); mService.buzzBeepBlinkLocked(r); - verifyNeverBeep(); } @@ -870,7 +892,6 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY; mService.buzzBeepBlinkLocked(summary); - verify(mUsageStats, never()).isAlertRateLimited(any()); } @@ -889,6 +910,30 @@ public class BuzzBeepBlinkTest extends NotificationTestCase { 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 61d999a80b33..c382e53802a6 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 android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; 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.media.AudioAttributes; 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.assertEquals; 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 @@ public class RankingHelperTest extends NotificationTestCase { 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 @@ public class RankingHelperTest extends NotificationTestCase { 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 @@ public class RankingHelperTest extends NotificationTestCase { } 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 @@ public class RankingHelperTest extends NotificationTestCase { 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 @@ public class RankingHelperTest extends NotificationTestCase { } @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 02f645a07a5f..c8dc9ff51a1b 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 @@ public class AccessibilityCacheTest { 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 ac196b5359ae..5523f7177069 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -288,7 +288,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { 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 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { 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 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } 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 82104034ca6e..163250d9d666 100644 --- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java +++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java @@ -296,6 +296,7 @@ public class NotificationTestList extends TestActivity 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 @@ public class NotificationTestList extends TestActivity 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 @@ public class NotificationTestList extends TestActivity Notification n = new Notification.Builder(NotificationTestList.this, "high") .setSmallIcon(R.drawable.icon2) .setContentTitle("High priority") + .setTicker("High priority") .build(); mNM.notify("high", 7006, n); } |