diff options
25 files changed, 711 insertions, 83 deletions
diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java index 251d346efb42..6956c8ac7135 100644 --- a/core/java/android/service/autofill/FillContext.java +++ b/core/java/android/service/autofill/FillContext.java @@ -114,7 +114,7 @@ public final class FillContext implements Parcelable { * * @hide */ - @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) { + @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId... ids) { final LinkedList<ViewNode> nodesToProcess = new LinkedList<>(); final ViewNode[] foundNodes = new AssistStructure.ViewNode[ids.length]; diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index a97babd74f2c..ef78559e2b53 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -454,7 +454,7 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb } private void updateOpaqueFlag() { - if (PixelFormat.formatHasAlpha(mRequestedFormat)) { + if (!PixelFormat.formatHasAlpha(mRequestedFormat)) { mSurfaceFlags |= SurfaceControl.OPAQUE; } else { mSurfaceFlags &= ~SurfaceControl.OPAQUE; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 10de5831de6f..2e817eb91cc4 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -4937,9 +4937,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager needGlobalAttributesUpdate(true); } ai.mKeepScreenOn = lastKeepOn; - if (!childHasFocus && child.hasFocusable()) { - focusableViewAvailable(child); - } } if (child.isLayoutDirectionInherited()) { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 1fef7cbd1953..c19ee56e7636 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -3842,11 +3842,12 @@ public class Editor { } if (mTextView.canRequestAutofill()) { - final int mode = mTextView.getText().length() <= 0 - ? MenuItem.SHOW_AS_ACTION_IF_ROOM : MenuItem.SHOW_AS_ACTION_NEVER; - menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL, - com.android.internal.R.string.autofill) - .setShowAsAction(mode); + final String selected = mTextView.getSelectedText(); + if (selected == null || selected.isEmpty()) { + menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL, + com.android.internal.R.string.autofill) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } } if (mTextView.canPasteAsPlainText()) { diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index 797cf2b6de56..d327180c6e9d 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -135,7 +135,7 @@ public class SystemNotificationChannels { channelsList.add(new NotificationChannel( FOREGROUND_SERVICE, context.getString(R.string.notification_channel_foreground_service), - NotificationManager.IMPORTANCE_MIN)); + NotificationManager.IMPORTANCE_LOW)); nm.createNotificationChannels(channelsList); } diff --git a/core/res/res/drawable/stat_sys_vitals.xml b/core/res/res/drawable/stat_sys_vitals.xml new file mode 100644 index 000000000000..213dd5fbed6e --- /dev/null +++ b/core/res/res/drawable/stat_sys_vitals.xml @@ -0,0 +1,29 @@ +<!-- +Copyright (C) 2017 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. +--> +<!-- "system vitals", as represented by an EKG trace --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M19.5608645,12.0797103 L17.15,5.15 L15.25,5.15 L11.95,15.95 L9.75,11.5 L7.95,11.55 L7.2,13.3 + L6.65,14.6 L3.25,14.6 L3.25,16.6 L7.35,16.6 L8,16.6 L8.25,16 L8.9,14.3 L11.2,18.85 L13.15,18.85 L16.25,8.8 + L17.5310733,12.642689 C17.2014325,12.9992627 17,13.4761078 17,14 C17,15.1045695 17.8954305,16 19,16 + C20.1045695,16 21,15.1045695 21,14 C21,13.0901368 20.3924276,12.3221796 19.5608645,12.0797103 Z M21,3 + C22,3 23,4 23,5 L23,19 C23,20 22,21 21,21 L3,21 C1.9,21 1,20.1 1,19 L1,5 C1,4 2,3 3,3 L21,3 Z" /> +</vector> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b88979d9dcaa..2f1df6fb9f06 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3039,10 +3039,10 @@ <java-symbol type="array" name="config_allowedSecureInstantAppSettings" /> <java-symbol type="bool" name="config_handleVolumeKeysInWindowManager" /> - <java-symbol type="integer" name="config_inCallNotificationVolumeRelative" /> - <java-symbol type="bool" name="config_dozeAlwaysOnDisplayAvailable" /> - <java-symbol type="integer" name="config_storageManagerDaystoRetainDefault" /> + + <java-symbol type="drawable" name="stat_sys_vitals" /> + </resources> diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java index da6dc7edbb5e..811bf2c43320 100644 --- a/core/tests/coretests/src/android/text/DynamicLayoutTest.java +++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java @@ -17,15 +17,14 @@ package android.text; import static android.text.Layout.Alignment.ALIGN_NORMAL; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import android.graphics.Canvas; +import android.graphics.Paint; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.style.ReplacementSpan; @@ -54,6 +53,16 @@ public class DynamicLayoutTest { assertNull(layout.getBlocksAlwaysNeedToBeRedrawn()); } + private class MockReplacementSpan extends ReplacementSpan { + public int getSize(Paint paint, CharSequence text, int start, int end, + Paint.FontMetricsInt fm) { + return 10; + } + + public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, + int y, int bottom, Paint paint) { } + } + @Test public void testGetBlocksAlwaysNeedToBeRedrawn_replacementSpan() { final SpannableStringBuilder builder = new SpannableStringBuilder(); @@ -66,17 +75,11 @@ public class DynamicLayoutTest { builder.append("hijk lmn\n"); assertNull(layout.getBlocksAlwaysNeedToBeRedrawn()); - ReplacementSpan mockReplacementSpan = mock(ReplacementSpan.class); - when(mockReplacementSpan.getSize(any(), any(), any(), any(), any())) - .thenReturn(10); - doNothing().when(mockReplacementSpan) - .draw(any(), any(), any(), any(), any(), any(), any(), any(), any()); - - builder.setSpan(mockReplacementSpan, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.setSpan(new MockReplacementSpan(), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); assertNotNull(layout.getBlocksAlwaysNeedToBeRedrawn()); assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0)); - builder.setSpan(mockReplacementSpan, 9, 13, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.setSpan(new MockReplacementSpan(), 9, 13, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0)); assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(1)); diff --git a/drm/java/android/drm/DrmUtils.java b/drm/java/android/drm/DrmUtils.java index 2a86996ef46a..60ee6d94949f 100644 --- a/drm/java/android/drm/DrmUtils.java +++ b/drm/java/android/drm/DrmUtils.java @@ -17,6 +17,7 @@ package android.drm; import java.io.BufferedInputStream; +import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -79,26 +80,16 @@ public class DrmUtils { file.delete(); } - private static void quietlyDispose(InputStream stream) { + private static void quietlyDispose(Closeable closable) { try { - if (null != stream) { - stream.close(); + if (null != closable) { + closable.close(); } } catch (IOException e) { // no need to care, at least as of now } } - private static void quietlyDispose(OutputStream stream) { - try { - if (null != stream) { - stream.close(); - } - } catch (IOException e) { - // no need to care - } - } - /** * Gets an instance of {@link DrmUtils.ExtendedMetadataParser}, which can be used to parse * extended metadata embedded in DRM constraint information. diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java index 228a6de6907c..dd731df0fbb0 100644 --- a/media/java/android/media/MediaHTTPConnection.java +++ b/media/java/android/media/MediaHTTPConnection.java @@ -136,7 +136,13 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { private void teardownConnection() { if (mConnection != null) { - mInputStream = null; + if (mInputStream != null) { + try { + mInputStream.close(); + } catch (IOException e) { + } + mInputStream = null; + } mConnection.disconnect(); mConnection = null; @@ -298,8 +304,7 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { mCurrentOffset = offset; } catch (IOException e) { mTotalSize = -1; - mInputStream = null; - mConnection = null; + teardownConnection(); mCurrentOffset = -1; throw e; diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 88350136b96b..fd994b572981 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -102,7 +102,7 @@ <!-- Speed label for slow network speed --> <string name="speed_label_slow">Slow</string> <!-- Speed label for okay network speed --> - <string name="speed_label_okay">Okay</string> + <string name="speed_label_okay">OK</string> <!-- Speed label for medium network speed --> <string name="speed_label_medium">Medium</string> <!-- Speed label for fast network speed --> diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 5197c95ce884..73a2a43adb8f 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -272,6 +272,9 @@ public class Dependency extends SystemUI { mProviders.put(TunablePaddingService.class, () -> new TunablePaddingService()); + mProviders.put(ForegroundServiceController.class, + () -> new ForegroundServiceControllerImpl(mContext)); + mProviders.put(UiOffloadThread.class, UiOffloadThread::new); // Put all dependencies above here so the factory can override them if it wants. diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java new file mode 100644 index 000000000000..a2c9ab4871c2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import android.service.notification.StatusBarNotification; + +public interface ForegroundServiceController { + /** + * @param sbn notification that was just posted + * @param importance + */ + void addNotification(StatusBarNotification sbn, int importance); + + /** + * @param sbn notification that was just changed in some way + * @param newImportance + */ + void updateNotification(StatusBarNotification sbn, int newImportance); + + /** + * @param sbn notification that was just canceled + */ + boolean removeNotification(StatusBarNotification sbn); + + /** + * @param userId + * @return true if this user has services missing notifications and therefore needs a + * disclosure notification. + */ + boolean isDungeonNeededForUser(int userId); + + /** + * @param sbn + * @return true if sbn is the system-provided "dungeon" (list of running foreground services). + */ + boolean isDungeonNotification(StatusBarNotification sbn); +} diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java new file mode 100644 index 000000000000..c930d567254a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Bundle; +import android.service.notification.StatusBarNotification; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto; + +import java.util.Arrays; + +/** + * Foreground service controller, a/k/a Dianne's Dungeon. + */ +public class ForegroundServiceControllerImpl + implements ForegroundServiceController { + private static final String TAG = "FgServiceController"; + private static final boolean DBG = false; + + private final SparseArray<UserServices> mUserServices = new SparseArray<>(); + private final Object mMutex = new Object(); + + public ForegroundServiceControllerImpl(Context context) { + } + + @Override + public boolean isDungeonNeededForUser(int userId) { + synchronized (mMutex) { + final UserServices services = mUserServices.get(userId); + if (services == null) return false; + return services.isDungeonNeeded(); + } + } + + @Override + public void addNotification(StatusBarNotification sbn, int importance) { + updateNotification(sbn, importance); + } + + @Override + public boolean removeNotification(StatusBarNotification sbn) { + synchronized (mMutex) { + final UserServices userServices = mUserServices.get(sbn.getUserId()); + if (userServices == null) { + if (DBG) { + Log.w(TAG, String.format( + "user %d with no known notifications got removeNotification for %s", + sbn.getUserId(), sbn)); + } + return false; + } + if (isDungeonNotification(sbn)) { + // if you remove the dungeon entirely, we take that to mean there are + // no running services + userServices.setRunningServices(null); + return true; + } else { + // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE + return userServices.removeNotification(sbn.getPackageName(), sbn.getKey()); + } + } + } + + @Override + public void updateNotification(StatusBarNotification sbn, int newImportance) { + synchronized (mMutex) { + UserServices userServices = mUserServices.get(sbn.getUserId()); + if (userServices == null) { + userServices = new UserServices(); + mUserServices.put(sbn.getUserId(), userServices); + } + + if (isDungeonNotification(sbn)) { + final Bundle extras = sbn.getNotification().extras; + if (extras != null) { + final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS); + userServices.setRunningServices(svcs); // null ok + } + } else { + userServices.removeNotification(sbn.getPackageName(), sbn.getKey()); + if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) + && newImportance > NotificationManager.IMPORTANCE_MIN) { + userServices.addNotification(sbn.getPackageName(), sbn.getKey()); + } + } + } + } + + @Override + public boolean isDungeonNotification(StatusBarNotification sbn) { + return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES + && sbn.getTag() == null + && sbn.getPackageName().equals("android"); + } + + /** + * Struct to track relevant packages and notifications for a userid's foreground services. + */ + private static class UserServices { + private String[] mRunning = null; + private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1); + public void setRunningServices(String[] pkgs) { + mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null; + } + public void addNotification(String pkg, String key) { + if (mNotifications.get(pkg) == null) { + mNotifications.put(pkg, new ArraySet<String>()); + } + mNotifications.get(pkg).add(key); + } + public boolean removeNotification(String pkg, String key) { + final boolean found; + final ArraySet<String> keys = mNotifications.get(pkg); + if (keys == null) { + found = false; + } else { + found = keys.remove(key); + if (keys.size() == 0) { + mNotifications.remove(pkg); + } + } + return found; + } + public boolean isDungeonNeeded() { + if (mRunning != null) { + for (String pkg : mRunning) { + final ArraySet<String> set = mNotifications.get(pkg); + if (set == null || set.size() == 0) { + return true; + } + } + } + return false; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 1844946e75b6..531437dff492 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -24,6 +24,8 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.Context; import android.graphics.drawable.Icon; +import android.os.AsyncTask; +import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; import android.service.notification.NotificationListenerService; @@ -38,8 +40,11 @@ import android.widget.RemoteViews; import android.Manifest; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.NotificationColorUtil; +import com.android.systemui.Dependency; +import com.android.systemui.ForegroundServiceController; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; @@ -339,6 +344,7 @@ public class NotificationData { mEntries.put(entry.notification.getKey(), entry); } mGroupManager.onEntryAdded(entry); + updateRankingAndSort(mRankingMap); } @@ -466,6 +472,10 @@ public class NotificationData { Collections.sort(mSortedAndFiltered, mRankingComparator); } + /** + * @param sbn + * @return true if this notification should NOT be shown right now + */ public boolean shouldFilterOut(StatusBarNotification sbn) { if (!(mEnvironment.isDeviceProvisioned() || showNotificationEvenIfUnprovisioned(sbn))) { @@ -487,6 +497,13 @@ public class NotificationData { && mGroupManager.isChildInGroupWithSummary(sbn)) { return true; } + + final ForegroundServiceController fsc = Dependency.get(ForegroundServiceController.class); + if (fsc.isDungeonNotification(sbn) && !fsc.isDungeonNeededForUser(sbn.getUserId())) { + // this is a foreground-service disclosure for a user that does not need to show one + return true; + } + return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 1934906a7d4a..a3c6e96f606b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -157,6 +157,7 @@ import com.android.systemui.DejankUtils; import com.android.systemui.DemoMode; import com.android.systemui.Dependency; import com.android.systemui.EventLogTags; +import com.android.systemui.ForegroundServiceController; import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; @@ -733,6 +734,7 @@ public class StatusBar extends SystemUI implements DemoMode, private boolean mClearAllEnabled; @Nullable private View mAmbientIndicationContainer; private ColorExtractor mColorExtractor; + private ForegroundServiceController mForegroundServiceController; private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { final int N = array.size(); @@ -782,6 +784,9 @@ public class StatusBar extends SystemUI implements DemoMode, mColorExtractor.addOnColorsChangedListener(this); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + + mForegroundServiceController = Dependency.get(ForegroundServiceController.class); + mDisplay = mWindowManager.getDefaultDisplay(); updateDisplaySize(); @@ -1635,6 +1640,10 @@ public class StatusBar extends SystemUI implements DemoMode, } } abortExistingInflation(key); + + mForegroundServiceController.addNotification(notification, + mNotificationData.getImportance(key)); + mPendingNotifications.put(key, shadeEntry); } @@ -1773,6 +1782,10 @@ public class StatusBar extends SystemUI implements DemoMode, return; } + if (entry != null) { + mForegroundServiceController.removeNotification(entry.notification); + } + if (entry != null && entry.row != null) { entry.row.setRemoved(); mStackScroller.cleanUpViewState(entry.row); @@ -6963,6 +6976,9 @@ public class StatusBar extends SystemUI implements DemoMode, entry.updateIcons(mContext, n); inflateViews(entry, mStackScroller); + mForegroundServiceController.updateNotification(notification, + mNotificationData.getImportance(key)); + boolean shouldPeek = shouldPeek(entry, notification); boolean alertAgain = alertAgain(entry, n); @@ -6980,6 +6996,7 @@ public class StatusBar extends SystemUI implements DemoMode, boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); } + setAreThereNotifications(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java new file mode 100644 index 000000000000..1f5255a0e869 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui; + +import android.annotation.UserIdInt; +import android.app.Notification; +import android.app.NotificationManager; +import android.os.Bundle; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import com.android.internal.messages.nano.SystemMessageProto; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ForegroundServiceControllerTest extends SysuiTestCase { + public static @UserIdInt int USERID_ONE = 10; // UserManagerService.MIN_USER_ID; + public static @UserIdInt int USERID_TWO = USERID_ONE + 1; + + private ForegroundServiceController fsc; + + @Before + public void setUp() throws Exception { + fsc = new ForegroundServiceControllerImpl(mContext); + } + + @Test + public void testNotificationCRUD() { + StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, "com.example.app1"); + StatusBarNotification sbn_user2_app2_fg = makeMockFgSBN(USERID_TWO, "com.example.app2"); + StatusBarNotification sbn_user1_app3_fg = makeMockFgSBN(USERID_ONE, "com.example.app3"); + StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1", + 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); + StatusBarNotification sbn_user2_app1 = makeMockSBN(USERID_TWO, "com.example.app1", + 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); + + assertFalse(fsc.removeNotification(sbn_user1_app3_fg)); + assertFalse(fsc.removeNotification(sbn_user2_app2_fg)); + assertFalse(fsc.removeNotification(sbn_user1_app1_fg)); + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.removeNotification(sbn_user2_app1)); + + fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); + fsc.addNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_DEFAULT); + fsc.addNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_DEFAULT); + fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); + fsc.addNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_DEFAULT); + + // these are never added to the tracker + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.removeNotification(sbn_user2_app1)); + + fsc.updateNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); + fsc.updateNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_DEFAULT); + // should still not be there + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.removeNotification(sbn_user2_app1)); + + fsc.updateNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_DEFAULT); + fsc.updateNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_DEFAULT); + fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); + + assertTrue(fsc.removeNotification(sbn_user1_app3_fg)); + assertFalse(fsc.removeNotification(sbn_user1_app3_fg)); + + assertTrue(fsc.removeNotification(sbn_user2_app2_fg)); + assertFalse(fsc.removeNotification(sbn_user2_app2_fg)); + + assertTrue(fsc.removeNotification(sbn_user1_app1_fg)); + assertFalse(fsc.removeNotification(sbn_user1_app1_fg)); + + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.removeNotification(sbn_user2_app1)); + } + + @Test + public void testDungeonPredicate() { + StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1", + 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); + StatusBarNotification sbn_user1_dungeon = makeMockSBN(USERID_ONE, "android", + SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES, + null, Notification.FLAG_NO_CLEAR); + + assertTrue(fsc.isDungeonNotification(sbn_user1_dungeon)); + assertFalse(fsc.isDungeonNotification(sbn_user1_app1)); + } + + @Test + public void testDungeonCRUD() { + StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1", + 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); + StatusBarNotification sbn_user1_dungeon = makeMockSBN(USERID_ONE, "android", + SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES, + null, Notification.FLAG_NO_CLEAR); + + fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); + fsc.addNotification(sbn_user1_dungeon, NotificationManager.IMPORTANCE_DEFAULT); + + fsc.removeNotification(sbn_user1_dungeon); + assertFalse(fsc.removeNotification(sbn_user1_app1)); + } + + @Test + public void testNeedsDungeonAfterRemovingUnrelatedNotification() { + final String PKG1 = "com.example.app100"; + + StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1, + 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); + StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1); + + // first add a normal notification + fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); + // nothing required yet + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); + // now the app starts a fg service + fsc.addNotification(makeMockDungeon(USERID_ONE, new String[]{ PKG1 }), + NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! + // add the fg notification + fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has got it covered + // remove the boring notification + fsc.removeNotification(sbn_user1_app1); + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has STILL got it covered + assertTrue(fsc.removeNotification(sbn_user1_app1_fg)); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! + } + + @Test + public void testSimpleAddRemove() { + final String PKG1 = "com.example.app1"; + final String PKG2 = "com.example.app2"; + + StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1, + 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); + fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); + + // no services are "running" + fsc.addNotification(makeMockDungeon(USERID_ONE, null), + NotificationManager.IMPORTANCE_DEFAULT); + + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + + fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG1}), + NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + + // switch to different package + fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG2}), + NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + + fsc.updateNotification(makeMockDungeon(USERID_TWO, new String[]{PKG1}), + NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); + assertTrue(fsc.isDungeonNeededForUser(USERID_TWO)); // finally user2 needs one too + + fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG2, PKG1}), + NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); + assertTrue(fsc.isDungeonNeededForUser(USERID_TWO)); + + fsc.removeNotification(makeMockDungeon(USERID_ONE, null /*unused*/)); + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); + assertTrue(fsc.isDungeonNeededForUser(USERID_TWO)); + + fsc.removeNotification(makeMockDungeon(USERID_TWO, null /*unused*/)); + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + } + + @Test + public void testDungeonBasic() { + final String PKG1 = "com.example.app0"; + + StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1, + 5000, "monkeys", Notification.FLAG_AUTO_CANCEL); + StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1); + + fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg + fsc.addNotification(makeMockDungeon(USERID_ONE, new String[]{ PKG1 }), + NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! + fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has got it covered + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + + // let's take out the other notification and see what happens. + + fsc.removeNotification(sbn_user1_app1); + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + + // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get + StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1); + sbn_user1_app1_fg_sneaky.getNotification().flags = 0; + fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + + // ok, ok, we'll put it back + sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; + fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT); + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + + assertTrue(fsc.removeNotification(sbn_user1_app1_fg_sneaky)); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required! + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + + // now let's test an upgrade + fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE; + fsc.updateNotification(sbn_user1_app1, + NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification + + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); + + // remove it, make sure we're out of compliance again + assertTrue(fsc.removeNotification(sbn_user1_app1)); // was fg, should return true + assertFalse(fsc.removeNotification(sbn_user1_app1)); + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); + + // finally, let's turn off the service + fsc.addNotification(makeMockDungeon(USERID_ONE, null), + NotificationManager.IMPORTANCE_DEFAULT); + + assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); + assertFalse(fsc.isDungeonNeededForUser(USERID_TWO)); + } + + private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag, + int flags) { + final Notification n = mock(Notification.class); + n.flags = flags; + return makeMockSBN(userid, pkg, id, tag, n); + } + private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag, + Notification n) { + final StatusBarNotification sbn = mock(StatusBarNotification.class); + when(sbn.getNotification()).thenReturn(n); + when(sbn.getId()).thenReturn(id); + when(sbn.getPackageName()).thenReturn(pkg); + when(sbn.getTag()).thenReturn(null); + when(sbn.getUserId()).thenReturn(userid); + when(sbn.getUser()).thenReturn(new UserHandle(userid)); + when(sbn.getKey()).thenReturn("MOCK:"+userid+"|"+pkg+"|"+id+"|"+tag); + return sbn; + } + private StatusBarNotification makeMockFgSBN(int userid, String pkg) { + return makeMockSBN(userid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE); + } + private StatusBarNotification makeMockDungeon(int userid, String[] pkgs) { + final Notification n = mock(Notification.class); + n.flags = Notification.FLAG_ONGOING_EVENT; + final Bundle extras = new Bundle(); + if (pkgs != null) extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs); + n.extras = extras; + final StatusBarNotification sbn = makeMockSBN(userid, "android", + SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES, + null, n); + sbn.getNotification().extras = extras; + return sbn; + } +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 980a7d41200f..073d7b20ed64 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -774,20 +774,29 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState break; } - final AutofillValue currentValue = viewState.getCurrentValue(); - if (currentValue == null || currentValue.isEmpty()) { - if (sDebug) { - Slog.d(TAG, "showSaveLocked(): empty value for required " + id ); + AutofillValue value = viewState.getCurrentValue(); + if (value == null || value.isEmpty()) { + final AutofillValue initialValue = getValueFromContexts(id); + if (initialValue != null) { + if (sDebug) { + Slog.d(TAG, "Value of required field " + id + " didn't change; " + + "using initial value (" + initialValue + ") instead"); + } + value = initialValue; + } else { + if (sDebug) { + Slog.d(TAG, "showSaveLocked(): empty value for required " + id ); + } + allRequiredAreNotEmpty = false; + break; } - allRequiredAreNotEmpty = false; - break; } final AutofillValue filledValue = viewState.getAutofilledValue(); - if (!currentValue.equals(filledValue)) { + if (!value.equals(filledValue)) { if (sDebug) { Slog.d(TAG, "showSaveLocked(): found a change on required " + id + ": " - + filledValue + " => " + currentValue); + + filledValue + " => " + value); } atLeastOneChanged = true; } @@ -845,6 +854,31 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** + * Gets the latest non-empty value for the given id in the autofill contexts. + */ + @Nullable + private AutofillValue getValueFromContexts(AutofillId id) { + AutofillValue value = null; + final int numContexts = mContexts.size(); + for (int i = 0; i < numContexts; i++) { + final FillContext context = mContexts.get(i); + // TODO: create a function that gets just one node so it doesn't create an array + // unnecessarily + final ViewNode[] nodes = context.findViewNodesByAutofillIds(id); + if (nodes != null) { + AutofillValue candidate = nodes[0].getAutofillValue(); + if (sDebug) { + Slog.d(TAG, "getValueFromContexts(" + id + ") at " + i + ": " + candidate); + } + if (candidate != null && !candidate.isEmpty()) { + value = candidate; + } + } + } + return value; + } + + /** * Calls service when user requested save. */ void callSaveLocked() { @@ -1009,7 +1043,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState || action == ACTION_VIEW_ENTERED) { if (sVerbose) Slog.v(TAG, "Creating viewState for " + id + " on " + action); boolean isIgnored = isIgnoredLocked(id); - viewState = new ViewState(this, id, value, this, + viewState = new ViewState(this, id, this, isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL); mViewStates.put(id, viewState); if (isIgnored) { @@ -1307,7 +1341,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (viewState != null) { viewState.setState(state); } else { - viewState = new ViewState(this, id, null, this, state); + viewState = new ViewState(this, id, this, state); if (sVerbose) { Slog.v(TAG, "Adding autofillable view with id " + id + " and state " + state); } @@ -1370,10 +1404,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + @Override + public String toString() { + return "Session: [id=" + id + ", pkg=" + mPackageName + "]"; + } + void dumpLocked(String prefix, PrintWriter pw) { final String prefix2 = prefix + " "; pw.print(prefix); pw.print("id: "); pw.println(id); pw.print(prefix); pw.print("uid: "); pw.println(uid); + pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName); pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); pw.print(prefix); pw.print("mResponses: "); if (mResponses == null) { diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java index cd8f4a59526f..51659bb8c8b3 100644 --- a/services/autofill/java/com/android/server/autofill/ViewState.java +++ b/services/autofill/java/com/android/server/autofill/ViewState.java @@ -74,16 +74,14 @@ final class ViewState { private final Session mSession; private FillResponse mResponse; - private AutofillValue mInitialValue; private AutofillValue mCurrentValue; private AutofillValue mAutofilledValue; private Rect mVirtualBounds; private int mState; - ViewState(Session session, AutofillId id, AutofillValue value, Listener listener, int state) { + ViewState(Session session, AutofillId id, Listener listener, int state) { mSession = session; this.id = id; - mInitialValue = value; mListener = listener; mState = state; } @@ -118,11 +116,6 @@ final class ViewState { } @Nullable - AutofillValue getInitialValue() { - return mInitialValue; - } - - @Nullable FillResponse getResponse() { return mResponse; } @@ -189,8 +182,8 @@ final class ViewState { @Override public String toString() { - return "ViewState: [id=" + id + ", initialValue=" + mInitialValue - + ", currentValue=" + mCurrentValue + ", autofilledValue=" + mAutofilledValue + return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue + + ", autofilledValue=" + mAutofilledValue + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() + "]"; } @@ -207,7 +200,6 @@ final class ViewState { pw.println(mResponse.getRequestId()); } } - pw.print(prefix); pw.print("initialValue:" ); pw.println(mInitialValue); pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue); pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue); pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds); diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index c3d550549a3f..8ffe8f547eac 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -332,6 +332,10 @@ public final class AutoFillUI { @android.annotation.UiThread private void hideSaveUiUiThread(@Nullable AutoFillUiCallback callback) { + if (sVerbose) { + Slog.v(TAG, "hideSaveUiUiThread(): mSaveUi=" + mSaveUi + ", callback=" + callback + + ", mCallback=" + mCallback); + } if (mSaveUi != null && (callback == null || callback == mCallback)) { mSaveUi.destroy(); mSaveUi = null; diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index d566d3d608b4..e9c98e94c860 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -398,6 +398,12 @@ final class FillUi { } catch (WindowManager.BadTokenException e) { if (sDebug) Slog.d(TAG, "Filed with with token " + params.token + " gone."); mCallback.onDestroy(); + } catch (IllegalStateException e) { + // WM throws an ISE if mContentView was added twice; this should never happen - + // since show() and hide() are always called in the UIThread - but when it does, + // it should not crash the system. + Slog.e(TAG, "Exception showing window " + params, e); + mCallback.onDestroy(); } } @@ -405,10 +411,18 @@ final class FillUi { * Hides the window. */ void hide() { - if (mShowing) { - mContentView.setOnTouchListener(null); - mWm.removeView(mContentView); - mShowing = false; + try { + if (mShowing) { + mContentView.setOnTouchListener(null); + mWm.removeView(mContentView); + mShowing = false; + } + } catch (IllegalStateException e) { + // WM might thrown an ISE when removing the mContentView; this should never + // happen - since show() and hide() are always called in the UIThread - but if it + // does, it should not crash the system. + Slog.e(TAG, "Exception hiding window ", e); + mCallback.onDestroy(); } } @@ -489,7 +503,7 @@ final class FillUi { final String value = item.getValue(); // No value, i.e. null, matches any filter if (value == null - || value.toLowerCase().contains(constraintLowerCase)) { + || value.toLowerCase().startsWith(constraintLowerCase)) { filteredItems.add(item); } } diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index d25ffced7797..491af91cbc36 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -17,6 +17,7 @@ package com.android.server.autofill.ui; import static com.android.server.autofill.Helper.sDebug; +import static com.android.server.autofill.Helper.sVerbose; import android.annotation.NonNull; import android.app.Dialog; @@ -63,7 +64,7 @@ final class SaveUi { @Override public void onSave() { - if (sDebug) Slog.d(TAG, "onSave(): " + mDone); + if (sDebug) Slog.d(TAG, "OneTimeListener.onSave(): " + mDone); if (mDone) { return; } @@ -73,7 +74,7 @@ final class SaveUi { @Override public void onCancel(IntentSender listener) { - if (sDebug) Slog.d(TAG, "onCancel(): " + mDone); + if (sDebug) Slog.d(TAG, "OneTimeListener.onCancel(): " + mDone); if (mDone) { return; } @@ -83,7 +84,7 @@ final class SaveUi { @Override public void onDestroy() { - if (sDebug) Slog.d(TAG, "onDestroy(): " + mDone); + if (sDebug) Slog.d(TAG, "OneTimeListener.onDestroy(): " + mDone); if (mDone) { return; } @@ -158,9 +159,8 @@ final class SaveUi { subTitleView.setVisibility(View.VISIBLE); } - Slog.i(TAG, "Showing save dialog: " + mTitle); if (sDebug) { - Slog.d(TAG, "SubTitle: " + mSubTitle); + Slog.d(TAG, "on constructor: title=" + mTitle + ", subTitle=" + mSubTitle); } final TextView noButton = view.findViewById(R.id.autofill_save_no); @@ -169,15 +169,15 @@ final class SaveUi { } else { noButton.setText(R.string.autofill_save_no); } - noButton.setOnClickListener((v) -> mListener.onCancel( - info.getNegativeActionListener())); + View.OnClickListener cancelListener = + (v) -> mListener.onCancel(info.getNegativeActionListener()); + noButton.setOnClickListener(cancelListener); final View yesButton = view.findViewById(R.id.autofill_save_yes); yesButton.setOnClickListener((v) -> mListener.onSave()); final View closeButton = view.findViewById(R.id.autofill_save_close); - closeButton.setOnClickListener((v) -> mListener.onCancel( - info.getNegativeActionListener())); + closeButton.setOnClickListener(cancelListener); mDialog = new Dialog(context, R.style.Theme_DeviceDefault_Light_Panel); mDialog.setContentView(view); @@ -195,13 +195,16 @@ final class SaveUi { params.width = WindowManager.LayoutParams.MATCH_PARENT; params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title); + Slog.i(TAG, "Showing save dialog: " + mTitle); mDialog.show(); } void destroy() { + if (sDebug) Slog.d(TAG, "destroy()"); throwIfDestroyed(); mListener.onDestroy(); mHandler.removeCallbacksAndMessages(mListener); + if (sVerbose) Slog.v(TAG, "destroy(): dismissing dialog"); mDialog.dismiss(); mDestroyed = true; } @@ -212,6 +215,11 @@ final class SaveUi { } } + @Override + public String toString() { + return mTitle == null ? "NO TITLE" : mTitle.toString(); + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("title: "); pw.println(mTitle); pw.print(prefix); pw.print("subtitle: "); pw.println(mSubTitle); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 2680b425ff3c..bad7091429a2 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -162,7 +162,7 @@ public final class ActiveServices { /** * Information about an app that is currently running one or more foreground services. - * (This mapps directly to the running apps we show in the notification.) + * (This maps directly to the running apps we show in the notification.) */ static final class ActiveForegroundApp { String mPackageName; @@ -813,6 +813,7 @@ public final class ActiveServices { String title; String msg; String[] pkgs; + long oldestStartTime = System.currentTimeMillis(); // now if (active.size() == 1) { intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromParts("package", active.get(0).mPackageName, null)); @@ -820,11 +821,13 @@ public final class ActiveServices { R.string.foreground_service_app_in_background, active.get(0).mLabel); msg = context.getString(R.string.foreground_service_tap_for_details); pkgs = new String[] { active.get(0).mPackageName }; + oldestStartTime = active.get(0).mStartTime; } else { intent = new Intent(Settings.ACTION_FOREGROUND_SERVICES_SETTINGS); pkgs = new String[active.size()]; for (int i = 0; i < active.size(); i++) { pkgs[i] = active.get(i).mPackageName; + oldestStartTime = Math.min(oldestStartTime, active.get(i).mStartTime); } intent.putExtra("packages", pkgs); title = context.getString( @@ -841,9 +844,10 @@ public final class ActiveServices { new Notification.Builder(context, SystemNotificationChannels.FOREGROUND_SERVICE) .addExtras(notificationBundle) - .setSmallIcon(R.drawable.ic_check_circle_24px) + .setSmallIcon(R.drawable.stat_sys_vitals) .setOngoing(true) - .setShowWhen(false) + .setShowWhen(oldestStartTime > 0) + .setWhen(oldestStartTime) .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)) .setContentTitle(title) diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java index ac7b763600a7..d1aecb1b8d1d 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -936,7 +936,7 @@ final class DefaultPermissionGrantPolicy { // permissions if the version on the system image does not declare them. if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) { PackageSetting sysPs = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName); - if (sysPs != null) { + if (sysPs != null && sysPs.pkg != null) { if (sysPs.pkg.requestedPermissions.isEmpty()) { return; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index b1154227b204..a64ab438feed 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -223,6 +223,7 @@ public class UserManagerService extends IUserManager.Stub { // Tron counters private static final String TRON_GUEST_CREATED = "users_guest_created"; private static final String TRON_USER_CREATED = "users_user_created"; + private static final String TRON_DEMO_CREATED = "users_demo_created"; private final Context mContext; private final PackageManagerService mPm; @@ -2523,7 +2524,8 @@ public class UserManagerService extends IUserManager.Stub { addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL, android.Manifest.permission.MANAGE_USERS); - MetricsLogger.count(mContext, isGuest ? TRON_GUEST_CREATED : TRON_USER_CREATED, 1); + MetricsLogger.count(mContext, isGuest ? TRON_GUEST_CREATED + : (isDemo ? TRON_DEMO_CREATED : TRON_USER_CREATED), 1); } finally { Binder.restoreCallingIdentity(ident); } |