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

Commit 9e3b8641 authored by Jason Monk's avatar Jason Monk
Browse files

Fix up slice service listener management

 - Make them indexed off binders so they can be tracked properly
 - Add death listener to update pinned state when a listener dies.

Test: cts + uiservicestests
Bug: 68378571
Change-Id: Ia80f9354e1a4b13790721e28da6c913e8cd311b2
parent 74848ae4
Loading
Loading
Loading
Loading
+41 −11
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.app.slice.SliceSpec;
import android.content.ContentProviderClient;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -49,11 +51,13 @@ public class PinnedSliceState {
    @GuardedBy("mLock")
    private final ArraySet<String> mPinnedPkgs = new ArraySet<>();
    @GuardedBy("mLock")
    private final ArraySet<ISliceListener> mListeners = new ArraySet<>();
    private final ArrayMap<IBinder, ISliceListener> mListeners = new ArrayMap<>();
    @GuardedBy("mLock")
    private SliceSpec[] mSupportedSpecs = null;
    @GuardedBy("mLock")
    private final ArrayMap<ISliceListener, String> mPkgMap = new ArrayMap<>();
    private final ArrayMap<IBinder, String> mPkgMap = new ArrayMap<>();

    private final DeathRecipient mDeathRecipient = this::handleRecheckListeners;

    public PinnedSliceState(SliceManagerService service, Uri uri) {
        mService = service;
@@ -107,20 +111,27 @@ public class PinnedSliceState {

    public void addSliceListener(ISliceListener listener, String pkg, SliceSpec[] specs) {
        synchronized (mLock) {
            if (mListeners.add(listener) && mListeners.size() == 1) {
            if (mListeners.size() == 0) {
                mService.listen(mUri);
            }
            mPkgMap.put(listener, pkg);
            try {
                listener.asBinder().linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
            }
            mListeners.put(listener.asBinder(), listener);
            mPkgMap.put(listener.asBinder(), pkg);
            mergeSpecs(specs);
        }
    }

    public boolean removeSliceListener(ISliceListener listener) {
        synchronized (mLock) {
            mPkgMap.remove(listener);
            if (mListeners.remove(listener) && mListeners.size() == 0) {
            listener.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mPkgMap.remove(listener.asBinder());
            if (mListeners.containsKey(listener.asBinder()) && mListeners.size() == 1) {
                mService.unlisten(mUri);
            }
            mListeners.remove(listener.asBinder());
        }
        return !isPinned();
    }
@@ -159,25 +170,44 @@ public class PinnedSliceState {
        return client;
    }

    private void handleRecheckListeners() {
        if (!isPinned()) return;
        synchronized (mLock) {
            for (int i = mListeners.size() - 1; i >= 0; i--) {
                ISliceListener l = mListeners.valueAt(i);
                if (!l.asBinder().isBinderAlive()) {
                    mListeners.removeAt(i);
                }
            }
            if (!isPinned()) {
                // All the listeners died, remove from pinned state.
                mService.removePinnedSlice(mUri);
            }
        }
    }

    private void handleBind() {
        Slice cachedSlice = doBind(null);
        synchronized (mLock) {
            mListeners.removeIf(l -> {
            if (!isPinned()) return;
            for (int i = mListeners.size() - 1; i >= 0; i--) {
                ISliceListener l = mListeners.valueAt(i);
                Slice s = cachedSlice;
                if (s == null || s.hasHint(Slice.HINT_CALLER_NEEDED)) {
                    s = doBind(mPkgMap.get(l));
                }
                if (s == null) {
                    return true;
                    mListeners.removeAt(i);
                    continue;
                }
                try {
                    l.onSliceUpdated(s);
                    return false;
                } catch (RemoteException e) {
                    Log.e(TAG, "Unable to notify slice " + mUri, e);
                    return true;
                    mListeners.removeAt(i);
                    continue;
                }
            }
            });
            if (!isPinned()) {
                // All the listeners died, remove from pinned state.
                mService.removePinnedSlice(mUri);
+3 −3
Original line number Diff line number Diff line
@@ -91,9 +91,9 @@ public class SliceManagerService extends ISliceManager.Stub {

        mObserver = new ContentObserver(mHandler) {
            @Override
            public void onChange(boolean selfChange, Uri uri) {
            public void onChange(boolean selfChange, Uri uri, int userId) {
                try {
                    getPinnedSlice(uri).onChange();
                    getPinnedSlice(maybeAddUserId(uri, userId)).onChange();
                } catch (IllegalStateException e) {
                    Log.e(TAG, "Received change for unpinned slice " + uri, e);
                }
@@ -204,7 +204,7 @@ public class SliceManagerService extends ISliceManager.Stub {
    }

    ///  ----- internal code -----
    void removePinnedSlice(Uri uri) {
    protected void removePinnedSlice(Uri uri) {
        synchronized (mLock) {
            mPinnedSlicesByUri.remove(uri).destroy();
        }
+33 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -21,8 +22,11 @@ import android.app.slice.SliceSpec;
import android.content.ContentProvider;
import android.content.IContentProvider;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -34,6 +38,7 @@ import com.android.server.UiServiceTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -147,6 +152,7 @@ public class PinnedSliceStateTest extends UiServiceTestCase {
    @Test
    public void testListenerPin() {
        ISliceListener listener = mock(ISliceListener.class);
        when(listener.asBinder()).thenReturn(new Binder());
        assertFalse(mPinnedSliceManager.isPinned());

        mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS);
@@ -159,7 +165,11 @@ public class PinnedSliceStateTest extends UiServiceTestCase {
    @Test
    public void testMultiListenerPin() {
        ISliceListener listener = mock(ISliceListener.class);
        Binder value = new Binder();
        when(listener.asBinder()).thenReturn(value);
        ISliceListener listener2 = mock(ISliceListener.class);
        Binder value2 = new Binder();
        when(listener2.asBinder()).thenReturn(value2);
        assertFalse(mPinnedSliceManager.isPinned());

        mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS);
@@ -171,9 +181,31 @@ public class PinnedSliceStateTest extends UiServiceTestCase {
        assertFalse(mPinnedSliceManager.isPinned());
    }

    @Test
    public void testListenerDeath() throws RemoteException {
        ISliceListener listener = mock(ISliceListener.class);
        IBinder binder = mock(IBinder.class);
        when(binder.isBinderAlive()).thenReturn(true);
        when(listener.asBinder()).thenReturn(binder);
        assertFalse(mPinnedSliceManager.isPinned());

        mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS);
        assertTrue(mPinnedSliceManager.isPinned());

        ArgumentCaptor<DeathRecipient> arg = ArgumentCaptor.forClass(DeathRecipient.class);
        verify(binder).linkToDeath(arg.capture(), anyInt());

        when(binder.isBinderAlive()).thenReturn(false);
        arg.getValue().binderDied();

        verify(mSliceService).removePinnedSlice(eq(TEST_URI));
        assertFalse(mPinnedSliceManager.isPinned());
    }

    @Test
    public void testPkgListenerPin() {
        ISliceListener listener = mock(ISliceListener.class);
        when(listener.asBinder()).thenReturn(new Binder());
        assertFalse(mPinnedSliceManager.isPinned());

        mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS);
@@ -191,6 +223,7 @@ public class PinnedSliceStateTest extends UiServiceTestCase {
        clearInvocations(mIContentProvider);

        ISliceListener listener = mock(ISliceListener.class);
        when(listener.asBinder()).thenReturn(new Binder());
        Slice s = new Slice.Builder(TEST_URI).build();
        Bundle b = new Bundle();
        b.putParcelable(SliceProvider.EXTRA_SLICE, s);