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

Commit 17d7b5cd authored by Iavor-Valentin Iftime's avatar Iavor-Valentin Iftime Committed by Android (Google) Code Review
Browse files

Merge "Add NotificationManager CallNotificationEventListener APIs" into main

parents e5eae654 6792d29d
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -1040,13 +1040,20 @@ package android.app {
    method @Nullable public android.content.ComponentName getAllowedNotificationAssistant();
    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public java.util.List<android.content.ComponentName> getEnabledNotificationListeners();
    method public boolean isNotificationAssistantAccessGranted(@NonNull android.content.ComponentName);
    method @FlaggedApi("android.service.notification.callstyle_callback_api") @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.ACCESS_NOTIFICATIONS}) public void registerCallNotificationEventListener(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.NotificationManager.CallNotificationEventListener);
    method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean);
    method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
    method @FlaggedApi("android.service.notification.callstyle_callback_api") @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.ACCESS_NOTIFICATIONS}) public void unregisterCallNotificationEventListener(@NonNull android.app.NotificationManager.CallNotificationEventListener);
    field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL = "android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL";
    field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_OPEN_NOTIFICATION_HANDLER_PANEL = "android.app.action.OPEN_NOTIFICATION_HANDLER_PANEL";
    field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL = "android.app.action.TOGGLE_NOTIFICATION_HANDLER_PANEL";
  }
  @FlaggedApi("android.service.notification.callstyle_callback_api") public static interface NotificationManager.CallNotificationEventListener {
    method @FlaggedApi("android.service.notification.callstyle_callback_api") public void onCallNotificationPosted(@NonNull String, @NonNull android.os.UserHandle);
    method @FlaggedApi("android.service.notification.callstyle_callback_api") public void onCallNotificationRemoved(@NonNull String, @NonNull android.os.UserHandle);
  }
  public final class RemoteLockscreenValidationResult implements android.os.Parcelable {
    method public int describeContents();
    method public int getResultCode();
+29 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024, 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 android.app;

import android.os.UserHandle;

/**
 * Callback to be called when a call notification is posted or removed
 *
 * @hide
 */
oneway interface ICallNotificationEventCallback {
    void onCallNotificationPosted(String packageName, in UserHandle userHandle);
    void onCallNotificationRemoved(String packageName, in UserHandle userHandle);
}
+7 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationHistory;
import android.app.NotificationManager;
import android.app.ICallNotificationEventCallback;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Intent;
@@ -247,4 +248,10 @@ interface INotificationManager

    @EnforcePermission("MANAGE_TOAST_RATE_LIMITING")
    void setToastRateLimitingEnabled(boolean enable);

    @EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"})
    void registerCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener);
    @EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"})
    void unregisterCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener);

}
+127 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app;

import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -69,6 +70,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * Class to notify the user of events that happen.  This is how you tell
@@ -627,6 +629,9 @@ public class NotificationManager {
     */
    public static int MAX_SERVICE_COMPONENT_NAME_LENGTH = 500;

    private final Map<CallNotificationEventListener, CallNotificationEventCallbackStub>
            mCallNotificationEventCallbacks = new HashMap<>();

    @UnsupportedAppUsage
    private static INotificationManager sService;

@@ -2848,4 +2853,126 @@ public class NotificationManager {
            default: return defValue;
        }
    }

    /**
     * Callback to receive updates when a call notification has been posted or removed
     * @hide
     */
    @SystemApi
    @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
    public interface CallNotificationEventListener {
        /**
         *  Called when a call notification was posted by a package this listener
         *  has registered for.
         * @param packageName package name of the app that posted the removed notification
         */
        @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
        void onCallNotificationPosted(@NonNull String packageName, @NonNull UserHandle userHandle);

        /**
         *  Called when a call notification was removed by a package this listener
         *  has registered for.
         * @param packageName package name of the app that removed notification
         */
        @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
        void onCallNotificationRemoved(@NonNull String packageName, @NonNull UserHandle userHandle);
    }

    private static class CallNotificationEventCallbackStub extends
            ICallNotificationEventCallback.Stub {
        final String mPackageName;
        final UserHandle mUserHandle;
        final Executor mExecutor;
        final CallNotificationEventListener mListener;

        CallNotificationEventCallbackStub(@NonNull String packageName,
                @NonNull UserHandle userHandle, @NonNull @CallbackExecutor Executor executor,
                @NonNull CallNotificationEventListener listener) {
            mPackageName = packageName;
            mUserHandle = userHandle;
            mExecutor = executor;
            mListener = listener;
        }

        @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
        @Override
        public void onCallNotificationPosted(String packageName, UserHandle userHandle) {
            mExecutor.execute(() -> mListener.onCallNotificationPosted(packageName, userHandle));
        }

        @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
        @Override
        public void onCallNotificationRemoved(String packageName, UserHandle userHandle) {
            mExecutor.execute(() -> mListener.onCallNotificationRemoved(packageName, userHandle));
        }
    }

    /**
     * Register a listener to be notified when a call notification is posted or removed
     * for a specific package and user.
     *
     * @param packageName Which package to monitor
     * @param userHandle Which user to monitor
     * @param executor Callback will run on this executor
     * @param listener Listener to register
     * @hide
     */
    @SystemApi
    @RequiresPermission(allOf = {
        android.Manifest.permission.INTERACT_ACROSS_USERS,
        android.Manifest.permission.ACCESS_NOTIFICATIONS})
    @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
    public void registerCallNotificationEventListener(@NonNull String packageName,
            @NonNull UserHandle userHandle, @NonNull @CallbackExecutor Executor executor,
            @NonNull CallNotificationEventListener listener) {
        checkRequired("packageName", packageName);
        checkRequired("userHandle", userHandle);
        checkRequired("executor", executor);
        checkRequired("listener", listener);
        INotificationManager service = getService();
        try {
            synchronized (mCallNotificationEventCallbacks) {
                CallNotificationEventCallbackStub callbackStub =
                        new CallNotificationEventCallbackStub(packageName, userHandle,
                                executor, listener);
                mCallNotificationEventCallbacks.put(listener, callbackStub);

                service.registerCallNotificationEventListener(packageName, userHandle,
                        callbackStub);
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Unregister a listener that was previously
     * registered with {@link #registerCallNotificationEventListener}
     *
     * @param listener Listener to unregister
     * @hide
     */
    @SystemApi
    @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
    @RequiresPermission(allOf = {
        android.Manifest.permission.INTERACT_ACROSS_USERS,
        android.Manifest.permission.ACCESS_NOTIFICATIONS})
    public void unregisterCallNotificationEventListener(
            @NonNull CallNotificationEventListener listener) {
        checkRequired("listener", listener);
        INotificationManager service = getService();
        try {
            synchronized (mCallNotificationEventCallbacks) {
                CallNotificationEventCallbackStub callbackStub =
                        mCallNotificationEventCallbacks.remove(listener);
                if (callbackStub != null) {
                    service.unregisterCallNotificationEventListener(callbackStub.mPackageName,
                            callbackStub.mUserHandle, callbackStub);
                }
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

}
+193 −0
Original line number Diff line number Diff line
@@ -95,9 +95,11 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
import static android.service.notification.Flags.callstyleCallbackApi;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -160,6 +162,7 @@ import android.Manifest;
import android.Manifest.permission;
import android.annotation.DurationMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.EnforcePermission;
import android.annotation.FlaggedApi;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -176,6 +179,7 @@ import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.IActivityManager;
import android.app.ICallNotificationEventCallback;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.ITransientNotificationCallback;
@@ -255,6 +259,7 @@ import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -729,6 +734,9 @@ public class NotificationManagerService extends SystemService {
    private NotificationUsageStats mUsageStats;
    private boolean mLockScreenAllowSecureNotifications = true;
    boolean mSystemExemptFromDismissal = false;
    final ArrayMap<String, ArrayMap<Integer,
            RemoteCallbackList<ICallNotificationEventCallback>>>
            mCallNotificationEventCallbacks = new ArrayMap<>();
    private static final int MY_UID = Process.myUid();
    private static final int MY_PID = Process.myPid();
@@ -4887,6 +4895,94 @@ public class NotificationManagerService extends SystemService {
            return new NotificationHistory();
        }
        /**
         * Register a listener to be notified when a call notification is posted or removed
         * for a specific package and user.
         * @param packageName Which package to monitor
         * @param userHandle Which user to monitor
         * @param listener Listener to register
         */
        @Override
        @EnforcePermission(allOf = {
                android.Manifest.permission.INTERACT_ACROSS_USERS,
                android.Manifest.permission.ACCESS_NOTIFICATIONS})
        public void registerCallNotificationEventListener(String packageName, UserHandle userHandle,
                ICallNotificationEventCallback listener) {
            registerCallNotificationEventListener_enforcePermission();
            final int userId = userHandle.getIdentifier() != UserHandle.USER_CURRENT
                    ? userHandle.getIdentifier() : mAmi.getCurrentUserId();
            synchronized (mCallNotificationEventCallbacks) {
                ArrayMap<Integer, RemoteCallbackList<ICallNotificationEventCallback>>
                        callbacksForPackage =
                        mCallNotificationEventCallbacks.getOrDefault(packageName, new ArrayMap<>());
                RemoteCallbackList<ICallNotificationEventCallback> callbackList =
                        callbacksForPackage.getOrDefault(userId, new RemoteCallbackList<>());
                if (callbackList.register(listener)) {
                    callbacksForPackage.put(userId, callbackList);
                    mCallNotificationEventCallbacks.put(packageName, callbacksForPackage);
                } else {
                    Log.e(TAG,
                            "registerCallNotificationEventListener failed to register listener: "
                                + packageName + " " + userHandle + " " + listener);
                    return;
                }
            }
            synchronized (mNotificationLock) {
                for (NotificationRecord r : mNotificationList) {
                    if (r.getNotification().isStyle(Notification.CallStyle.class)
                            && notificationMatchesUserId(r, userId, false)
                            && r.getSbn().getPackageName().equals(packageName)) {
                        try {
                            listener.onCallNotificationPosted(packageName, r.getUser());
                        } catch (RemoteException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
        /**
         * Unregister a listener that was previously
         * registered with {@link #registerCallNotificationEventListener}
         *
         * @param packageName Which package to stop monitoring
         * @param userHandle Which user to stop monitoring
         * @param listener Listener to unregister
         */
        @Override
        @EnforcePermission(allOf = {
            android.Manifest.permission.INTERACT_ACROSS_USERS,
            android.Manifest.permission.ACCESS_NOTIFICATIONS})
        public void unregisterCallNotificationEventListener(String packageName,
                    UserHandle userHandle, ICallNotificationEventCallback listener) {
            unregisterCallNotificationEventListener_enforcePermission();
            synchronized (mCallNotificationEventCallbacks) {
                final int userId = userHandle.getIdentifier() != UserHandle.USER_CURRENT
                        ? userHandle.getIdentifier() : mAmi.getCurrentUserId();
                ArrayMap<Integer, RemoteCallbackList<ICallNotificationEventCallback>>
                        callbacksForPackage = mCallNotificationEventCallbacks.get(packageName);
                if (callbacksForPackage == null) {
                    return;
                }
                RemoteCallbackList<ICallNotificationEventCallback> callbackList =
                        callbacksForPackage.get(userId);
                if (callbackList == null) {
                    return;
                }
                if (!callbackList.unregister(listener)) {
                    Log.e(TAG,
                            "unregisterCallNotificationEventListener listener not found for: "
                            + packageName + " " + userHandle + " " + listener);
                }
            }
        }
        /**
         * Register a listener binder directly with the notification manager.
         *
@@ -8698,6 +8794,11 @@ public class NotificationManagerService extends SystemService {
                                }
                            });
                        }
                        if (callstyleCallbackApi()) {
                            notifyCallNotificationEventListenerOnRemoved(r);
                        }
                        // ATTENTION: in a future release we will bail out here
                        // so that we do not play sounds, show lights, etc. for invalid
                        // notifications
@@ -10055,6 +10156,9 @@ public class NotificationManagerService extends SystemService {
                        mGroupHelper.onNotificationRemoved(r.getSbn());
                    }
                });
                if (callstyleCallbackApi()) {
                    notifyCallNotificationEventListenerOnRemoved(r);
                }
            }
            if (Flags.refactorAttentionHelper()) {
@@ -11729,6 +11833,10 @@ public class NotificationManagerService extends SystemService {
                mNotificationRecordLogger.logNotificationPosted(report);
            }
        });
        if (callstyleCallbackApi()) {
            notifyCallNotificationEventListenerOnPosted(r);
        }
    }
    @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
@@ -12785,6 +12893,91 @@ public class NotificationManagerService extends SystemService {
        }
    }
    @GuardedBy("mNotificationLock")
    private void broadcastToCallNotificationEventCallbacks(
            final RemoteCallbackList<ICallNotificationEventCallback> callbackList,
            final NotificationRecord r,
            boolean isPosted) {
        if (callbackList != null) {
            int numCallbacks = callbackList.beginBroadcast();
            try {
                for (int i = 0; i < numCallbacks; i++) {
                    if (isPosted) {
                        callbackList.getBroadcastItem(i)
                                .onCallNotificationPosted(r.getSbn().getPackageName(), r.getUser());
                    } else {
                        callbackList.getBroadcastItem(i)
                                .onCallNotificationRemoved(r.getSbn().getPackageName(),
                                    r.getUser());
                    }
                }
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
            callbackList.finishBroadcast();
        }
    }
    @GuardedBy("mNotificationLock")
    void notifyCallNotificationEventListenerOnPosted(final NotificationRecord r) {
        if (!r.getNotification().isStyle(Notification.CallStyle.class)) {
            return;
        }
        synchronized (mCallNotificationEventCallbacks) {
            ArrayMap<Integer, RemoteCallbackList<ICallNotificationEventCallback>>
                    callbacksForPackage =
                    mCallNotificationEventCallbacks.get(r.getSbn().getPackageName());
            if (callbacksForPackage == null) {
                return;
            }
            if (!r.getUser().equals(UserHandle.ALL)) {
                broadcastToCallNotificationEventCallbacks(
                        callbacksForPackage.get(r.getUser().getIdentifier()), r, true);
                // Also notify the listeners registered for USER_ALL
                broadcastToCallNotificationEventCallbacks(callbacksForPackage.get(USER_ALL), r,
                        true);
            } else {
                // Notify listeners registered for any userId
                for (RemoteCallbackList<ICallNotificationEventCallback> callbackList
                        : callbacksForPackage.values()) {
                    broadcastToCallNotificationEventCallbacks(callbackList, r, true);
                }
            }
        }
    }
    @GuardedBy("mNotificationLock")
    void notifyCallNotificationEventListenerOnRemoved(final NotificationRecord r) {
        if (!r.getNotification().isStyle(Notification.CallStyle.class)) {
            return;
        }
        synchronized (mCallNotificationEventCallbacks) {
            ArrayMap<Integer, RemoteCallbackList<ICallNotificationEventCallback>>
                    callbacksForPackage =
                    mCallNotificationEventCallbacks.get(r.getSbn().getPackageName());
            if (callbacksForPackage == null) {
                return;
            }
            if (!r.getUser().equals(UserHandle.ALL)) {
                broadcastToCallNotificationEventCallbacks(
                        callbacksForPackage.get(r.getUser().getIdentifier()), r, false);
                // Also notify the listeners registered for USER_ALL
                broadcastToCallNotificationEventCallbacks(callbacksForPackage.get(USER_ALL), r,
                        false);
            } else {
                // Notify listeners registered for any userId
                for (RemoteCallbackList<ICallNotificationEventCallback> callbackList
                        : callbacksForPackage.values()) {
                    broadcastToCallNotificationEventCallbacks(callbackList, r, false);
                }
            }
        }
    }
    // TODO (b/194833441): remove when we've fully migrated to a permission
    class RoleObserver implements OnRoleHoldersChangedListener {
        // Role name : user id : list of approved packages
Loading