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

Commit 204eba6c authored by Marin Shalamanov's avatar Marin Shalamanov
Browse files

Add eventMask to DM.registerDisplayListener

Add a new parameter to DisplayManager.registDisplayListener
which clients can use to specify in which events
they are interested.

Test: atest DisplayManagerServiceTest
      atest DisplayManagerGlobalTest

Bug: 171240622
Change-Id: Icc71bdd9ddb390bc5406b298a557c84194a53c00
parent d96c8193
Loading
Loading
Loading
Loading
+62 −2
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY;

import android.Manifest;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -376,6 +377,43 @@ public final class DisplayManager {
    @TestApi
    public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2;

    /**
     * @hide
     */
    @LongDef(flag = true, prefix = {"EVENT_FLAG_"}, value = {
            EVENT_FLAG_DISPLAY_ADDED,
            EVENT_FLAG_DISPLAY_CHANGED,
            EVENT_FLAG_DISPLAY_REMOVED,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface EventsMask {}

    /**
     * Event type for when a new display is added.
     *
     * @see #registerDisplayListener(DisplayListener, Handler, long)
     *
     * @hide
     */
    public static final long EVENT_FLAG_DISPLAY_ADDED = 1L << 0;

    /**
     * Event type for when a display is removed.
     *
     * @see #registerDisplayListener(DisplayListener, Handler, long)
     *
     * @hide
     */
    public static final long EVENT_FLAG_DISPLAY_REMOVED = 1L << 1;

    /**
     * Event type for when a display is changed.
     *
     * @see #registerDisplayListener(DisplayListener, Handler, long)
     *
     * @hide
     */
    public static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2;

    /** @hide */
    public DisplayManager(Context context) {
@@ -486,7 +524,7 @@ public final class DisplayManager {
    }

    /**
     * Registers an display listener to receive notifications about when
     * Registers a display listener to receive notifications about when
     * displays are added, removed or changed.
     *
     * @param listener The listener to register.
@@ -496,7 +534,29 @@ public final class DisplayManager {
     * @see #unregisterDisplayListener
     */
    public void registerDisplayListener(DisplayListener listener, Handler handler) {
        mGlobal.registerDisplayListener(listener, handler);
        registerDisplayListener(listener, handler, EVENT_FLAG_DISPLAY_ADDED
                | EVENT_FLAG_DISPLAY_CHANGED | EVENT_FLAG_DISPLAY_REMOVED);
    }

    /**
     * Registers a display listener to receive notifications about given display event types.
     *
     * @param listener The listener to register.
     * @param handler The handler on which the listener should be invoked, or null
     * if the listener should be invoked on the calling thread's looper.
     * @param eventsMask A bitmask of the event types for which this listener is subscribed.
     *
     * @see #EVENT_FLAG_DISPLAY_ADDED
     * @see #EVENT_FLAG_DISPLAY_CHANGED
     * @see #EVENT_FLAG_DISPLAY_REMOVED
     * @see #registerDisplayListener(DisplayListener, Handler)
     * @see #unregisterDisplayListener
     *
     * @hide
     */
    public void registerDisplayListener(@NonNull DisplayListener listener,
            @Nullable Handler handler, @EventsMask long eventsMask) {
        mGlobal.registerDisplayListener(listener, handler, eventsMask);
    }

    /**
+70 −15
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package android.hardware.display;

import static android.hardware.display.DisplayManager.EventsMask;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PropertyInvalidatedCache;
@@ -42,6 +45,10 @@ import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
import android.view.Surface;

import com.android.internal.annotations.VisibleForTesting;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -66,6 +73,14 @@ public final class DisplayManagerGlobal {
    // orientation change before the display info cache has actually been invalidated.
    private static final boolean USE_CACHE = false;

    @IntDef(prefix = {"SWITCHING_TYPE_"}, value = {
            EVENT_DISPLAY_ADDED,
            EVENT_DISPLAY_CHANGED,
            EVENT_DISPLAY_REMOVED,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface DisplayEvent {}

    public static final int EVENT_DISPLAY_ADDED = 1;
    public static final int EVENT_DISPLAY_CHANGED = 2;
    public static final int EVENT_DISPLAY_REMOVED = 3;
@@ -81,16 +96,17 @@ public final class DisplayManagerGlobal {
    private final IDisplayManager mDm;

    private DisplayManagerCallback mCallback;
    private final ArrayList<DisplayListenerDelegate> mDisplayListeners =
            new ArrayList<DisplayListenerDelegate>();
    private @EventsMask long mRegisteredEventsMask = 0;
    private final ArrayList<DisplayListenerDelegate> mDisplayListeners = new ArrayList<>();

    private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
    private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<>();
    private final ColorSpace mWideColorSpace;
    private int[] mDisplayIdCache;

    private int mWifiDisplayScanNestCount;

    private DisplayManagerGlobal(IDisplayManager dm) {
    @VisibleForTesting
    public DisplayManagerGlobal(IDisplayManager dm) {
        mDm = dm;
        try {
            mWideColorSpace =
@@ -274,18 +290,25 @@ public final class DisplayManagerGlobal {
     * If that is still null, a runtime exception will be thrown.
     */
    public void registerDisplayListener(@NonNull DisplayListener listener,
            @Nullable Handler handler) {
            @Nullable Handler handler, @EventsMask long eventsMask) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
        }

        if (eventsMask == 0) {
            throw new IllegalArgumentException("The set of events to listen to must not be empty.");
        }

        synchronized (mLock) {
            int index = findDisplayListenerLocked(listener);
            if (index < 0) {
                Looper looper = getLooperForHandler(handler);
                mDisplayListeners.add(new DisplayListenerDelegate(listener, looper));
                mDisplayListeners.add(new DisplayListenerDelegate(listener, looper, eventsMask));
                registerCallbackIfNeededLocked();
            } else {
                mDisplayListeners.get(index).setEventsMask(eventsMask);
            }
            updateCallbackIfNeededLocked();
        }
    }

@@ -300,6 +323,7 @@ public final class DisplayManagerGlobal {
                DisplayListenerDelegate d = mDisplayListeners.get(index);
                d.clearEvents();
                mDisplayListeners.remove(index);
                updateCallbackIfNeededLocked();
            }
        }
    }
@@ -325,18 +349,36 @@ public final class DisplayManagerGlobal {
        return -1;
    }

    @EventsMask
    private int calculateEventsMaskLocked() {
        int mask = 0;
        final int numListeners = mDisplayListeners.size();
        for (int i = 0; i < numListeners; i++) {
            mask |= mDisplayListeners.get(i).mEventsMask;
        }
        return mask;
    }

    private void registerCallbackIfNeededLocked() {
        if (mCallback == null) {
            mCallback = new DisplayManagerCallback();
            updateCallbackIfNeededLocked();
        }
    }

    private void updateCallbackIfNeededLocked() {
        int mask = calculateEventsMaskLocked();
        if (mask != mRegisteredEventsMask) {
            try {
                mDm.registerCallback(mCallback);
                mDm.registerCallbackWithEventMask(mCallback, mask);
                mRegisteredEventsMask = mask;
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

    private void handleDisplayEvent(int displayId, int event) {
    private void handleDisplayEvent(int displayId, @DisplayEvent int event) {
        synchronized (mLock) {
            if (USE_CACHE) {
                mDisplayInfoCache.remove(displayId);
@@ -754,7 +796,7 @@ public final class DisplayManagerGlobal {

    private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
        @Override
        public void onDisplayEvent(int displayId, int event) {
        public void onDisplayEvent(int displayId, @DisplayEvent int event) {
            if (DEBUG) {
                Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);
            }
@@ -764,13 +806,16 @@ public final class DisplayManagerGlobal {

    private static final class DisplayListenerDelegate extends Handler {
        public final DisplayListener mListener;
        public long mEventsMask;

        DisplayListenerDelegate(DisplayListener listener, @NonNull Looper looper) {
        DisplayListenerDelegate(DisplayListener listener, @NonNull Looper looper,
                @EventsMask long eventsMask) {
            super(looper, null, true /*async*/);
            mListener = listener;
            mEventsMask = eventsMask;
        }

        public void sendDisplayEvent(int displayId, int event) {
        public void sendDisplayEvent(int displayId, @DisplayEvent int event) {
            Message msg = obtainMessage(event, displayId, 0);
            sendMessage(msg);
        }
@@ -779,17 +824,27 @@ public final class DisplayManagerGlobal {
            removeCallbacksAndMessages(null);
        }

        public synchronized void setEventsMask(@EventsMask long newEventsMask) {
            mEventsMask = newEventsMask;
        }

        @Override
        public void handleMessage(Message msg) {
        public synchronized void handleMessage(Message msg) {
            switch (msg.what) {
                case EVENT_DISPLAY_ADDED:
                    if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) {
                        mListener.onDisplayAdded(msg.arg1);
                    }
                    break;
                case EVENT_DISPLAY_CHANGED:
                    if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
                        mListener.onDisplayChanged(msg.arg1);
                    }
                    break;
                case EVENT_DISPLAY_REMOVED:
                    if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) {
                        mListener.onDisplayRemoved(msg.arg1);
                    }
                    break;
            }
        }
+1 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ interface IDisplayManager {
    boolean isUidPresentOnDisplay(int uid, int displayId);

    void registerCallback(in IDisplayManagerCallback callback);
    void registerCallbackWithEventMask(in IDisplayManagerCallback callback, long eventsMask);

    // Requires CONFIGURE_WIFI_DISPLAY permission.
    // The process must have previously registered a callback.
+127 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021 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.hardware.display;

import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;

import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class DisplayManagerGlobalTest {

    private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;

    @Mock
    private IDisplayManager mDisplayManager;

    @Mock
    private DisplayManager.DisplayListener mListener;

    @Captor
    private ArgumentCaptor<IDisplayManagerCallback> mCallbackCaptor;

    private Context mContext;
    private DisplayManagerGlobal mDisplayManagerGlobal;
    private Handler mHandler;

    @Before
    public void setUp() throws RemoteException {
        MockitoAnnotations.initMocks(this);
        Mockito.when(mDisplayManager.getPreferredWideGamutColorSpaceId()).thenReturn(0);
        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        mHandler = mContext.getMainThreadHandler();
        mDisplayManagerGlobal = new DisplayManagerGlobal(mDisplayManager);
    }

    @Test
    public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
        Mockito.verify(mDisplayManager)
                .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
        IDisplayManagerCallback callback = mCallbackCaptor.getValue();

        int displayId = 1;
        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
        waitForHandler();
        Mockito.verify(mListener).onDisplayAdded(eq(displayId));
        Mockito.verifyNoMoreInteractions(mListener);

        Mockito.reset(mListener);
        callback.onDisplayEvent(1, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
        waitForHandler();
        Mockito.verify(mListener).onDisplayChanged(eq(displayId));
        Mockito.verifyNoMoreInteractions(mListener);

        Mockito.reset(mListener);
        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
        waitForHandler();
        Mockito.verify(mListener).onDisplayRemoved(eq(displayId));
        Mockito.verifyNoMoreInteractions(mListener);
    }

    @Test
    public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
        // First we subscribe to all events in order to test that the subsequent calls to
        // registerDisplayListener will update the event mask.
        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
        Mockito.verify(mDisplayManager)
                .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
        IDisplayManagerCallback callback = mCallbackCaptor.getValue();

        int displayId = 1;
        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED);
        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
        waitForHandler();
        Mockito.verifyZeroInteractions(mListener);

        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
        waitForHandler();
        Mockito.verifyZeroInteractions(mListener);

        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
        waitForHandler();
        Mockito.verifyZeroInteractions(mListener);
    }

    private void waitForHandler() {
        mHandler.runWithScissors(() -> { }, 0);
    }
}
+51 −10
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT;
import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.hardware.display.DisplayManager.EventsMask;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
@@ -28,6 +29,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLI
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static android.hardware.display.DisplayManagerGlobal.DisplayEvent;
import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
@@ -125,6 +127,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;

/**
@@ -820,14 +823,16 @@ public final class DisplayManagerService extends SystemService {
    }

    private void registerCallbackInternal(IDisplayManagerCallback callback, int callingPid,
            int callingUid) {
            int callingUid, @EventsMask long eventsMask) {
        synchronized (mSyncRoot) {
            if (mCallbacks.get(callingPid) != null) {
                throw new SecurityException("The calling process has already "
                        + "registered an IDisplayManagerCallback.");
            CallbackRecord record = mCallbacks.get(callingPid);

            if (record != null) {
                record.updateEventsMask(eventsMask);
                return;
            }

            CallbackRecord record = new CallbackRecord(callingPid, callingUid, callback);
            record = new CallbackRecord(callingPid, callingUid, callback, eventsMask);
            try {
                IBinder binder = callback.asBinder();
                binder.linkToDeath(record, 0);
@@ -1695,7 +1700,7 @@ public final class DisplayManagerService extends SystemService {
        }
    }

    private void sendDisplayEventLocked(int displayId, int event) {
    private void sendDisplayEventLocked(int displayId, @DisplayEvent int event) {
        Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
        mHandler.sendMessage(msg);
    }
@@ -1724,7 +1729,8 @@ public final class DisplayManagerService extends SystemService {

    // Runs on Handler thread.
    // Delivers display event notifications to callbacks.
    private void deliverDisplayEvent(int displayId, ArraySet<Integer> uids, int event) {
    private void deliverDisplayEvent(int displayId, ArraySet<Integer> uids,
            @DisplayEvent int event) {
        if (DEBUG) {
            Slog.d(TAG, "Delivering display event: displayId="
                    + displayId + ", event=" + event);
@@ -2059,13 +2065,20 @@ public final class DisplayManagerService extends SystemService {
        public final int mPid;
        public final int mUid;
        private final IDisplayManagerCallback mCallback;
        private @EventsMask AtomicLong mEventsMask;

        public boolean mWifiDisplayScanRequested;

        CallbackRecord(int pid, int uid, IDisplayManagerCallback callback) {
        CallbackRecord(int pid, int uid, IDisplayManagerCallback callback,
                @EventsMask long eventsMask) {
            mPid = pid;
            mUid = uid;
            mCallback = callback;
            mEventsMask = new AtomicLong(eventsMask);
        }

        public void updateEventsMask(@EventsMask long eventsMask) {
            mEventsMask.set(eventsMask);
        }

        @Override
@@ -2076,7 +2089,11 @@ public final class DisplayManagerService extends SystemService {
            onCallbackDied(this);
        }

        public void notifyDisplayEventAsync(int displayId, int event) {
        public void notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
            if (!shouldSendEvent(event)) {
                return;
            }

            try {
                mCallback.onDisplayEvent(displayId, event);
            } catch (RemoteException ex) {
@@ -2085,6 +2102,22 @@ public final class DisplayManagerService extends SystemService {
                binderDied();
            }
        }

        private boolean shouldSendEvent(@DisplayEvent int event) {
            final long mask = mEventsMask.get();
            switch (event) {
                case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
                    return (mask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0;
                case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
                    return (mask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0;
                case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED:
                    return (mask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0;
                default:
                    // This should never happen.
                    Slog.e(TAG, "Unknown display event " + event);
                    return true;
            }
        }
    }

    @VisibleForTesting
@@ -2149,6 +2182,14 @@ public final class DisplayManagerService extends SystemService {

        @Override // Binder call
        public void registerCallback(IDisplayManagerCallback callback) {
            registerCallbackWithEventMask(callback, DisplayManager.EVENT_FLAG_DISPLAY_ADDED
                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
        }

        @Override // Binder call
        public void registerCallbackWithEventMask(IDisplayManagerCallback callback,
                @EventsMask long eventsMask) {
            if (callback == null) {
                throw new IllegalArgumentException("listener must not be null");
            }
@@ -2157,7 +2198,7 @@ public final class DisplayManagerService extends SystemService {
            final int callingUid = Binder.getCallingUid();
            final long token = Binder.clearCallingIdentity();
            try {
                registerCallbackInternal(callback, callingPid, callingUid);
                registerCallbackInternal(callback, callingPid, callingUid, eventsMask);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
Loading