Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 51674980 authored by Kevin F. Haggerty's avatar Kevin F. Haggerty
Browse files

Merge tag 'android-8.1.0_r72' into staging/lineage-15.1_merge-android-8.1.0_r72

Android 8.1.0 release 72

* tag 'android-8.1.0_r72':
  Force FGS notifications to show for a minimum time
  Prevent system uid component from running in an isolated app process
  Only allow INSTALL_ALLOW_TEST from shell or root
  DO NOT MERGE Validate wallpaper dimension while generating crop
  RESTRICT AUTOMERGE Revive runLimit check logic

Change-Id: Ib7e1d04e646ff2ebf943bf1154626de3435f4ba9
parents 898cdb65 15273ad9
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar;

import android.annotation.NonNull;
import android.app.Notification;
import android.os.Handler;
import android.os.Looper;
import android.util.ArraySet;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.NotificationData;

/**
 * Extends the lifetime of foreground notification services such that they show for at least
 * five seconds
 */
public class ForegroundServiceLifetimeExtender implements NotificationLifetimeExtender {
    private static final String TAG = "FGSLifetimeExtender";

    @VisibleForTesting
    public static final int MIN_FGS_TIME_MS = 5000;

    private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback;
    private ArraySet<NotificationData.Entry> mManagedEntries = new ArraySet<>();
    private Handler mHandler = new Handler(Looper.getMainLooper());

    public ForegroundServiceLifetimeExtender() {
    }

    @Override
    public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) {
        mNotificationSafeToRemoveCallback = callback;
    }

    @Override
    public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) {
        if ((entry.notification.getNotification().flags
                & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
            return false;
        }
        long currentTime = System.currentTimeMillis();
        return currentTime - entry.notification.getPostTime() < MIN_FGS_TIME_MS;
    }

    @Override
    public boolean shouldExtendLifetimeForPendingNotification(
            @NonNull NotificationData.Entry entry) {
        return shouldExtendLifetime(entry);
    }

    @Override
    public void setShouldManageLifetime(
            @NonNull NotificationData.Entry entry, boolean shouldManage) {
        android.util.Log.d("FGSExtender", "setShouldManageLifetime " + shouldManage);
        if (!shouldManage) {
            mManagedEntries.remove(entry);
            return;
        }
        mManagedEntries.add(entry);
        Runnable r = () -> {
            if (mManagedEntries.contains(entry)) {
                mManagedEntries.remove(entry);
                if (mNotificationSafeToRemoveCallback != null) {
                    mNotificationSafeToRemoveCallback.onSafeToRemove(entry.key);
                }
            }
        };
        long delayAmt = MIN_FGS_TIME_MS
                - (System.currentTimeMillis() - entry.notification.getPostTime());
        mHandler.postDelayed(r, delayAmt);
    }
}
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar;

import android.annotation.NonNull;

import com.android.systemui.statusbar.NotificationData;

/**
 * Interface for anything that may need to keep notifications managed even after
 * {@link NotificationListener} removes it.  The lifetime extender is in charge of performing the
 * callback when the notification is then safe to remove.
 */
public interface NotificationLifetimeExtender {

    /**
     * Set the handler to callback to when the notification is safe to remove.
     *
     * @param callback the handler to callback
     */
    void setCallback(@NonNull NotificationSafeToRemoveCallback callback);

    /**
     * Determines whether or not the extender needs the notification kept after removal.
     *
     * @param entry the entry containing the notification to check
     * @return true if the notification lifetime should be extended
     */
    boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry);

    /**
     * It's possible that a notification was canceled before it ever became visible. This callback
     * gives lifetime extenders a chance to make sure it shows up. For example if a foreground
     * service is canceled too quickly but we still want to make sure a FGS notification shows.
     * @param pendingEntry the canceled (but pending) entry
     * @return true if the notification lifetime should be extended
     */
    default boolean shouldExtendLifetimeForPendingNotification(
            @NonNull NotificationData.Entry pendingEntry) {
        return false;
    }

    /**
     * Sets whether or not the lifetime should be managed by the extender.  In practice, if
     * shouldManage is true, this is where the extender starts managing the entry internally and is
     * now responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)}
     * when the entry is safe to remove.  If shouldManage is false, the extender no longer needs to
     * worry about it (either because we will be removing it anyway or the entry is no longer
     * removed due to an update).
     *
     * @param entry the entry that needs an extended lifetime
     * @param shouldManage true if the extender should manage the entry now, false otherwise
     */
    void setShouldManageLifetime(@NonNull NotificationData.Entry entry, boolean shouldManage);

    /**
     * The callback for when the notification is now safe to remove (i.e. its lifetime has ended).
     */
    interface NotificationSafeToRemoveCallback {
        /**
         * Called when the lifetime extender determines it's safe to remove.
         *
         * @param key key of the entry that is now safe to remove
         */
        void onSafeToRemove(String key);
    }
}
+57 −1
Original line number Diff line number Diff line
@@ -113,6 +113,7 @@ import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -209,6 +210,7 @@ import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ForegroundServiceLifetimeExtender;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -216,6 +218,7 @@ import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.NotificationGuts;
import com.android.systemui.statusbar.NotificationInfo;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.RemoteInputController;
@@ -843,9 +846,9 @@ public class StatusBar extends SystemUI implements DemoMode,
    @Nullable private View mAmbientIndicationContainer;
    private String mKeyToRemoveOnGutsClosed;
    private SysuiColorExtractor mColorExtractor;
    private ForegroundServiceController mForegroundServiceController;
    private ScreenLifecycle mScreenLifecycle;
    @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
    protected ForegroundServiceController mForegroundServiceController;

    private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
        final int N = array.size();
@@ -899,6 +902,9 @@ public class StatusBar extends SystemUI implements DemoMode,
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);

        mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
        mFGSExtender = new ForegroundServiceLifetimeExtender();
        mFGSExtender.setCallback(key -> removeNotification(key, mLatestRankingMap));


        mDisplay = mWindowManager.getDefaultDisplay();
        updateDisplaySize();
@@ -1936,6 +1942,11 @@ public class StatusBar extends SystemUI implements DemoMode,
        }
        Entry entry = mNotificationData.get(key);

        if (entry != null && mFGSExtender.shouldExtendLifetime(entry)) {
            extendLifetime(entry, mFGSExtender);
            return;
        }

        if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
                && (entry.row != null && !entry.row.isDismissed())) {
            mLatestRankingMap = ranking;
@@ -1974,9 +1985,35 @@ public class StatusBar extends SystemUI implements DemoMode,
                }
            }
        }
        // Make sure no lifetime extension is happening anymore
        cancelLifetimeExtension(entry);
        setAreThereNotifications();
    }

    /** Lifetime extension keeps entries around after they would've otherwise been canceled */
    private void extendLifetime(Entry entry, NotificationLifetimeExtender extender) {
        // Cancel any other extender which might be holding on to this notification entry
        NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry);
        if (activeExtender != null && activeExtender != extender) {
            activeExtender.setShouldManageLifetime(entry, false);
        }
        mRetainedNotifications.put(entry, extender);
        extender.setShouldManageLifetime(entry, true);
    }

    /** Tells the current extender (if any) to stop extending the entry's lifetime */
    private void cancelLifetimeExtension(NotificationData.Entry entry) {
        NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry);
        if (activeExtender != null) {
            activeExtender.setShouldManageLifetime(entry, false);
        }
    }

    @VisibleForTesting
    public Map<Entry, NotificationLifetimeExtender> getRetainedNotificationMap() {
        return mRetainedNotifications;
    }

    /**
     * Ensures that the group children are cancelled immediately when the group summary is cancelled
     * instead of waiting for the notification manager to send all cancels. Otherwise this could
@@ -3800,6 +3837,17 @@ public class StatusBar extends SystemUI implements DemoMode,
            }
        }

        pw.println("  Lifetime-extended notifications:");
        if (mRetainedNotifications.isEmpty()) {
            pw.println("    None");
        } else {
            for (Map.Entry<NotificationData.Entry, NotificationLifetimeExtender> entry
                    : mRetainedNotifications.entrySet()) {
                pw.println("    " + entry.getKey().notification + " retained by "
                        + entry.getValue().getClass().getName());
            }
        }

        pw.print("  mInteractingWindows="); pw.println(mInteractingWindows);
        pw.print("  mStatusBarWindowState=");
        pw.println(windowStateToString(mStatusBarWindowState));
@@ -6022,6 +6070,11 @@ public class StatusBar extends SystemUI implements DemoMode,

    protected RemoteInputController mRemoteInputController;

    // A lifetime extender that watches for foreground service notifications
    @VisibleForTesting protected NotificationLifetimeExtender mFGSExtender;
    private final Map<Entry, NotificationLifetimeExtender> mRetainedNotifications =
        new ArrayMap<>();

    // for heads up notifications
    protected HeadsUpManager mHeadsUpManager;

@@ -7710,6 +7763,9 @@ public class StatusBar extends SystemUI implements DemoMode,
            Log.w(TAG, "Notification that was kept for guts was updated. " + key);
        }

        // No need to keep the lifetime extension around if an update comes in for it
        cancelLifetimeExtension(entry);

        Notification n = notification.getNotification();
        mNotificationData.updateRanking(ranking);

+85 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar;

import static com.android.systemui.statusbar.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.app.Notification;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.StatusBarNotification;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationData.Entry;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class ForegroundServiceLifetimeExtenderTest extends SysuiTestCase {
    private ForegroundServiceLifetimeExtender mExtender = new ForegroundServiceLifetimeExtender();
    private StatusBarNotification mSbn;
    private NotificationData.Entry mEntry;
    private Notification mNotif;

    @Before
    public void setup() {
        mNotif = new Notification.Builder(mContext, "")
                .setSmallIcon(R.drawable.ic_person)
                .setContentTitle("Title")
                .setContentText("Text")
                .build();
        mSbn = mock(StatusBarNotification.class);
        when(mSbn.getNotification()).thenReturn(mNotif);
        mEntry = new NotificationData.Entry(mSbn);
    }

    /**
     * ForegroundServiceLifetimeExtenderTest
     */
    @Test
    public void testShouldExtendLifetime_should_foreground() {
        // Extend the lifetime of a FGS notification iff it has not been visible
        // for the minimum time
        mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
        when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis());
        assertTrue(mExtender.shouldExtendLifetime(mEntry));
    }

    @Test
    public void testShouldExtendLifetime_shouldNot_foreground() {
        mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
        when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
        assertFalse(mExtender.shouldExtendLifetime(mEntry));
    }

    @Test
    public void testShouldExtendLifetime_shouldNot_notForeground() {
        mNotif.flags = 0;
        when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
        assertFalse(mExtender.shouldExtendLifetime(mEntry));
    }
}
+78 −3
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone;

import static android.app.NotificationManager.IMPORTANCE_HIGH;

import static com.android.systemui.statusbar.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;

import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.fail;
@@ -33,6 +35,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.Notification;
import android.app.trust.TrustManager;
import android.content.Context;
@@ -47,6 +50,7 @@ import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.service.notification.NotificationListenerService.RankingMap;
import android.support.test.filters.SmallTest;
import android.support.test.metricshelper.MetricsAsserts;
import android.testing.AndroidTestingRunner;
@@ -65,7 +69,11 @@ import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.R;
import com.android.systemui.statusbar.ForegroundServiceLifetimeExtender;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -89,13 +97,20 @@ import org.junit.runner.RunWith;

import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;

import java.util.ArrayList;
import java.util.Map;

import junit.framework.Assert;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class StatusBarTest extends SysuiTestCase {

    private static final String TEST_PACKAGE_NAME = "test";
    private static final int TEST_UID = 123;

    StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
    UnlockMethodCache mUnlockMethodCache;
    KeyguardIndicationController mKeyguardIndicationController;
@@ -108,8 +123,12 @@ public class StatusBarTest extends SysuiTestCase {
    SystemServicesProxy mSystemServicesProxy;
    NotificationPanelView mNotificationPanelView;
    IStatusBarService mBarService;
    RemoteInputController mRemoteInputController;
    ForegroundServiceController mForegroundServiceController;
    ArrayList<Entry> mNotificationList;
    private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
    private ForegroundServiceLifetimeExtender mFGSExtender =
        new ForegroundServiceLifetimeExtender();

    @Before
    public void setup() throws Exception {
@@ -135,6 +154,8 @@ public class StatusBarTest extends SysuiTestCase {
        when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
        mNotificationList = mock(ArrayList.class);
        IPowerManager powerManagerService = mock(IPowerManager.class);
        mRemoteInputController = mock(RemoteInputController.class);
        mForegroundServiceController = mock(ForegroundServiceController.class);
        HandlerThread handlerThread = new HandlerThread("TestThread");
        handlerThread.start();
        mPowerManager = new PowerManager(mContext, powerManagerService,
@@ -143,10 +164,11 @@ public class StatusBarTest extends SysuiTestCase {
        mBarService = mock(IStatusBarService.class);

        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
        mFGSExtender.setCallback(key -> mStatusBar.removeNotification(key, mock(RankingMap.class)));
        mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
                mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
                mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView,
                mBarService);
                mBarService, mFGSExtender, mRemoteInputController, mForegroundServiceController);
        mStatusBar.mContext = mContext;
        mStatusBar.mComponents = mContext.getComponents();
        doAnswer(invocation -> {
@@ -427,6 +449,54 @@ public class StatusBarTest extends SysuiTestCase {
    }


    @Test
    public void testForegroundServiceNotificationKeptForFiveSeconds() throws Exception {
        RankingMap rm = mock(RankingMap.class);

        // sbn posted "just now"
        Notification n = new Notification.Builder(mContext, "")
                .setSmallIcon(R.drawable.ic_person)
                .setContentTitle("Title")
                .setContentText("Text")
                .build();
        n.flags |= Notification.FLAG_FOREGROUND_SERVICE;
        StatusBarNotification sbn =
                new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
                0, n, new UserHandle(ActivityManager.getCurrentUser()), null,
                System.currentTimeMillis());
        NotificationData.Entry entry = new NotificationData.Entry(sbn);
        when(mNotificationData.get(any())).thenReturn(entry);
        mStatusBar.removeNotification(sbn.getKey(), rm);
        Map<NotificationData.Entry, NotificationLifetimeExtender> map =
                mStatusBar.getRetainedNotificationMap();
        Assert.assertTrue(map.containsKey(entry));
    }

    @Test
    public void testForegroundServiceNotification_notRetainedIfShownForFiveSeconds() 
        throws Exception {

        RankingMap rm = mock(RankingMap.class);

        // sbn posted "more than 5 seconds ago"
        Notification n = new Notification.Builder(mContext, "")
                .setSmallIcon(R.drawable.ic_person)
                .setContentTitle("Title")
                .setContentText("Text")
                .build();
        n.flags |= Notification.FLAG_FOREGROUND_SERVICE;
        StatusBarNotification sbn =
            new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
                0, n, new UserHandle(ActivityManager.getCurrentUser()), null,
                System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
        NotificationData.Entry entry = new NotificationData.Entry(sbn);
        when(mNotificationData.get(any())).thenReturn(entry);
        mStatusBar.removeNotification(sbn.getKey(), rm);
        Map<NotificationData.Entry, NotificationLifetimeExtender> map =
                mStatusBar.getRetainedNotificationMap();
        Assert.assertFalse(map.containsKey(entry));
    }

    @Test
    public void testLogHidden() {
        try {
@@ -515,7 +585,8 @@ public class StatusBarTest extends SysuiTestCase {
                UnlockMethodCache unlock, KeyguardIndicationController key,
                NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
                PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView,
                IStatusBarService barService) {
                IStatusBarService barService, ForegroundServiceLifetimeExtender fgsExtender,
                RemoteInputController ric, ForegroundServiceController fsc) {
            mStatusBarKeyguardViewManager = man;
            mUnlockMethodCache = unlock;
            mKeyguardIndicationController = key;
@@ -527,6 +598,10 @@ public class StatusBarTest extends SysuiTestCase {
            mSystemServicesProxy = ssp;
            mNotificationPanel = panelView;
            mBarService = barService;
            mFGSExtender = fgsExtender;
            mRemoteInputController = ric;
            mForegroundServiceController = fsc;

            mWakefulnessLifecycle = createAwakeWakefulnessLifecycle();
            mScrimController = mock(ScrimController.class);
        }
Loading