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

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

Merge tag 'android-9.0.0_r51' into staging/lineage-16.0_merge-android-9.0.0_r51

Android 9.0.0 Release 51 (5948683)

* tag 'android-9.0.0_r51':
  Force FGS notifications to show for a minimum time
  RESTRICT AUTOMERGE Do not compute outside given range in TextLine
  DO NOT MERGE revoke certain app-ops on suspend

Change-Id: I42efc81a4b9eba07070350610c4c1cef657f8f10
parents 7ef25210 90449053
Loading
Loading
Loading
Loading
+3 −9
Original line number Diff line number Diff line
@@ -257,9 +257,7 @@ public class TextLine {
        for (int i = 0; i < runs.length; i += 2) {
            int runStart = runs[i];
            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
            if (runLimit > mLen) {
                runLimit = mLen;
            }
            if (runStart > mLen) break;
            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;

            int segstart = runStart;
@@ -335,9 +333,7 @@ public class TextLine {
        for (int i = 0; i < runs.length; i += 2) {
            int runStart = runs[i];
            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
            if (runLimit > mLen) {
                runLimit = mLen;
            }
            if (runStart > mLen) break;
            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;

            int segstart = runStart;
@@ -424,9 +420,7 @@ public class TextLine {
        for (int i = 0; i < runs.length; i += 2) {
            int runStart = runs[i];
            int runLimit = runStart + (runs[i + 1] & Layout.RUN_LENGTH_MASK);
            if (runLimit > mLen) {
                runLimit = mLen;
            }
            if (runStart > mLen) break;
            boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0;

            int segstart = runStart;
+89 −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
    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());

    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) {
        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);
    }
}
+64 −1
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Log;
import android.view.View;
@@ -71,6 +72,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
@@ -116,6 +118,12 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
    private final SmartReplyController mSmartReplyController =
            Dependency.get(SmartReplyController.class);

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

    protected IStatusBarService mBarService;
    protected NotificationPresenter mPresenter;
    protected Callback mCallback;
@@ -238,6 +246,16 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
                pw.println(entry.notification);
            }
        }
        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("  mUseHeadsUp=");
        pw.println(mUseHeadsUp);
        pw.print("  mKeysKeptForRemoteInput: ");
@@ -252,6 +270,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
        mMessagingUtil = new NotificationMessagingUtil(context);
        mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
        mGroupManager.setPendingEntries(mPendingNotifications);
        mFGSExtender.setCallback(key -> removeNotification(key, mLatestRankingMap));
    }

    public void setUpWithPresenter(NotificationPresenter presenter,
@@ -300,6 +319,12 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
        mOnAppOpsClickListener = mGutsManager::openGuts;
    }

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

    public NotificationData getNotificationData() {
        return mNotificationData;
    }
@@ -485,8 +510,17 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.

    @Override
    public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
        boolean deferRemoval = false;
        // First chance to extend the lifetime of a notification
        NotificationData.Entry pendingEntry = mPendingNotifications.get(key);
        if (pendingEntry != null) {
            if (mFGSExtender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
                extendLifetime(pendingEntry, mFGSExtender);
                return;
            }
        }

        abortExistingInflation(key);
        boolean deferRemoval = false;
        if (mHeadsUpManager.isHeadsUp(key)) {
            // A cancel() in response to a remote input shouldn't be delayed, as it makes the
            // sending look longer than it takes.
@@ -545,6 +579,11 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
            }
        }

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

        // Actually removing notification so smart reply controller can forget about it.
        mSmartReplyController.stopSending(entry);

@@ -580,9 +619,30 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
        handleGroupSummaryRemoved(key);
        StatusBarNotification old = removeNotificationViews(key, ranking);

        // Make sure no lifetime extension is happening anymore
        cancelLifetimeExtension(entry);
        mCallback.onNotificationRemoved(key, old);
    }

    private void extendLifetime(
            NotificationData.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);
    }

    private void cancelLifetimeExtension(NotificationData.Entry entry) {
        NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry);
        if (activeExtender != null) {
            activeExtender.setShouldManageLifetime(entry, false);
        }
    }

    public StatusBarNotification rebuildNotificationWithRemoteInput(NotificationData.Entry entry,
            CharSequence remoteInputText, boolean showSpinner) {
        StatusBarNotification sbn = entry.notification;
@@ -872,6 +932,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
            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
        cancelLifetimeExtension(entry);

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

+65 −0
Original line number Diff line number Diff line
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);
    }
}
+88 −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));
    }
}
 No newline at end of file
Loading