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

Commit 5330e7f8 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Properly remove local color areas of callback" into sc-v2-dev am: eb57e090

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16332363

Change-Id: Ic1d9afedbb9b6dbfa85aa8671e7995b09bf3daf9
parents 5929c63f eb57e090
Loading
Loading
Loading
Loading
+44 −37
Original line number Diff line number Diff line
@@ -365,19 +365,20 @@ public class WallpaperManager {
        private int mCachedWallpaperUserId;
        private Bitmap mDefaultWallpaper;
        private Handler mMainLooperHandler;
        private ArrayMap<RectF, ArraySet<LocalWallpaperColorConsumer>> mLocalColorAreas =
        private ArrayMap<LocalWallpaperColorConsumer, ArraySet<RectF>> mLocalColorCallbackAreas =
                        new ArrayMap<>();
        private ILocalWallpaperColorConsumer mLocalColorCallback =
                new ILocalWallpaperColorConsumer.Stub() {
                    @Override
                    public void onColorsChanged(RectF area, WallpaperColors colors) {
                        ArraySet<LocalWallpaperColorConsumer> callbacks =
                                mLocalColorAreas.get(area);
                        if (callbacks == null) return;
                        for (LocalWallpaperColorConsumer callback: callbacks) {
                        for (LocalWallpaperColorConsumer callback :
                                mLocalColorCallbackAreas.keySet()) {
                            ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
                            if (areas != null && areas.contains(area)) {
                                callback.onColorsChanged(area, colors);
                            }
                        }
                    }
                };

        Globals(IWallpaperManager service, Looper looper) {
@@ -420,17 +421,20 @@ public class WallpaperManager {
            }
        }

        public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
        public void addOnColorsChangedListener(
                @NonNull LocalWallpaperColorConsumer callback,
                @NonNull List<RectF> regions, int which, int userId, int displayId) {
            synchronized (this) {
                for (RectF area : regions) {
                ArraySet<LocalWallpaperColorConsumer> callbacks = mLocalColorAreas.get(area);
                if (callbacks == null) {
                    callbacks = new ArraySet<>();
                    mLocalColorAreas.put(area, callbacks);
                    ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
                    if (areas == null) {
                        areas = new ArraySet<>();
                        mLocalColorCallbackAreas.put(callback, areas);
                    }
                callbacks.add(callback);
                    areas.add(area);
                }
                try {
                    // one way returns immediately
                    mService.addOnLocalColorsChangedListener(mLocalColorCallback, regions, which,
                            userId, displayId);
                } catch (RemoteException e) {
@@ -438,30 +442,33 @@ public class WallpaperManager {
                    Log.e(TAG, "Can't register for local color updates", e);
                }
            }
        }

        public void removeOnColorsChangedListener(
                @NonNull LocalWallpaperColorConsumer callback, int which, int userId,
                int displayId) {
            final ArrayList<RectF> removeAreas = new ArrayList<>();
            for (RectF area : mLocalColorAreas.keySet()) {
                ArraySet<LocalWallpaperColorConsumer> callbacks = mLocalColorAreas.get(area);
                if (callbacks == null) continue;
                callbacks.remove(callback);
                if (callbacks.size() == 0) {
                    mLocalColorAreas.remove(area);
                    removeAreas.add(area);
            synchronized (this) {
                final ArraySet<RectF> removeAreas = mLocalColorCallbackAreas.remove(callback);
                if (removeAreas == null || removeAreas.size() == 0) {
                    return;
                }
                for (LocalWallpaperColorConsumer cb : mLocalColorCallbackAreas.keySet()) {
                    ArraySet<RectF> areas = mLocalColorCallbackAreas.get(cb);
                    if (areas != null && cb != callback) removeAreas.removeAll(areas);
                }
                try {
                    if (removeAreas.size() > 0) {
                        // one way returns immediately
                        mService.removeOnLocalColorsChangedListener(
                            mLocalColorCallback, removeAreas, which, userId, displayId);
                                mLocalColorCallback, new ArrayList(removeAreas), which, userId,
                                displayId);
                    }
                } catch (RemoteException e) {
                    // Can't get colors, connection lost.
                    Log.e(TAG, "Can't unregister for local color updates", e);
                }
            }
        }

        /**
         * Stop listening to wallpaper color events.
+166 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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 com.android.server.wallpaper;

import android.app.ILocalWallpaperColorConsumer;
import android.graphics.RectF;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * Manages the lifecycle of local wallpaper color callbacks and their interested wallpaper regions.
 */
public class LocalColorRepository {
    /**
     * Maps local wallpaper color callbacks' binders to their interested wallpaper regions, which
     * are stored in a map of display Ids to wallpaper regions.
     * binder callback -> [display id: int] -> areas
     */
    ArrayMap<IBinder, SparseArray<ArraySet<RectF>>> mLocalColorAreas = new ArrayMap();
    RemoteCallbackList<ILocalWallpaperColorConsumer> mCallbacks = new RemoteCallbackList();

    /**
     * Add areas to a consumer
     * @param consumer
     * @param areas
     * @param displayId
     */
    public void addAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas, int displayId) {
        IBinder binder = consumer.asBinder();
        SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder);
        ArraySet<RectF> displayAreas = null;
        if (displays == null) {
            try {
                consumer.asBinder().linkToDeath(() ->
                        mLocalColorAreas.remove(consumer.asBinder()), 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            displays = new SparseArray<>();
            mLocalColorAreas.put(binder, displays);
        } else {
            displayAreas = displays.get(displayId);
        }
        if (displayAreas == null) {
            displayAreas = new ArraySet(areas);
            displays.put(displayId, displayAreas);
        }

        for (int i = 0; i < areas.size(); i++) {
            displayAreas.add(areas.get(i));
        }
        mCallbacks.register(consumer);
    }

    /**
     * remove an area for a consumer
     * @param consumer
     * @param areas
     * @param displayId
     * @return the areas that are removed from all callbacks
     */
    public List<RectF> removeAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas,
            int displayId) {
        IBinder binder = consumer.asBinder();
        SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder);
        ArraySet<RectF> registeredAreas = null;
        if (displays != null) {
            registeredAreas = displays.get(displayId);
            if (registeredAreas == null) {
                mCallbacks.unregister(consumer);
            } else {
                for (int i = 0; i < areas.size(); i++) {
                    registeredAreas.remove(areas.get(i));
                }
                if (registeredAreas.size() == 0) {
                    displays.remove(displayId);
                }
            }
            if (displays.size() == 0) {
                mLocalColorAreas.remove(binder);
                mCallbacks.unregister(consumer);
            }
        } else {
            mCallbacks.unregister(consumer);
        }
        ArraySet<RectF> purged = new ArraySet<>(areas);
        for (int i = 0; i < mLocalColorAreas.size(); i++) {
            for (int j = 0; j < mLocalColorAreas.valueAt(i).size(); j++) {
                for (int k = 0; k < mLocalColorAreas.valueAt(i).valueAt(j).size(); k++) {
                    purged.remove(mLocalColorAreas.valueAt(i).valueAt(j).valueAt(k));
                }
            }
        }
        return new ArrayList(purged);
    }

    /**
     * Return the local areas by display id
     * @param displayId
     * @return
     */
    public List<RectF> getAreasByDisplayId(int displayId) {
        ArrayList<RectF> areas = new ArrayList();
        for (int i = 0; i < mLocalColorAreas.size(); i++) {
            SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.valueAt(i);
            if (displays == null) continue;
            ArraySet<RectF> displayAreas = displays.get(displayId);
            if (displayAreas == null) continue;
            for (int j = 0; j < displayAreas.size(); j++) {
                areas.add(displayAreas.valueAt(j));
            }
        }
        return areas;
    }

    /**
     * invoke a callback for each area of interest
     * @param callback
     * @param area
     * @param displayId
     */
    public void forEachCallback(Consumer<ILocalWallpaperColorConsumer> callback,
            RectF area, int displayId) {
        mCallbacks.broadcast(cb -> {
            IBinder binder = cb.asBinder();
            SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder);
            if (displays == null) return;
            ArraySet<RectF> displayAreas = displays.get(displayId);
            if (displayAreas != null && displayAreas.contains(area)) callback.accept(cb);
        });
    }

    /**
     * For testing
     * @param callback
     * @return if the callback is registered
     */
    @VisibleForTesting
    protected boolean isCallbackAvailable(ILocalWallpaperColorConsumer callback) {
        return mLocalColorAreas.get(callback.asBinder()) != null;
    }
}
+18 −86
Original line number Diff line number Diff line
@@ -89,8 +89,6 @@ import android.service.wallpaper.IWallpaperService;
import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
@@ -881,12 +879,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
    private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
    private int mCurrentUserId = UserHandle.USER_NULL;
    private boolean mInAmbientMode;
    private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas =
            new ArrayMap<>();
    private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>>
            mLocalColorAreaCallbacks = new ArrayMap<>();
    private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>();
    private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>();
    private LocalColorRepository mLocalColorRepo = new LocalColorRepository();

    static class WallpaperData {

@@ -1305,24 +1298,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
        public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors,
                int displayId) {
            forEachDisplayConnector(displayConnector -> {
                if (displayConnector.mDisplayId == displayId) {
                    RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks;
                    ArrayMap<IBinder, Integer> callbackDisplayIds;
                    synchronized (mLock) {
                        callbacks = mLocalColorAreaCallbacks.get(area);
                        callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId);
                    }
                    if (callbacks == null) return;
                    callbacks.broadcast(c -> {
                Consumer<ILocalWallpaperColorConsumer> callback = cb -> {
                    try {
                            Integer targetDisplayId =
                                    callbackDisplayIds.get(c.asBinder());
                            if (targetDisplayId == null) return;
                            if (targetDisplayId == displayId) c.onColorsChanged(area, colors);
                        cb.onColorsChanged(area, colors);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    });
                };
                synchronized (mLock) {
                    // it is safe to make an IPC call since it is one way (returns immediately)
                    mLocalColorRepo.forEachCallback(callback, area, displayId);
                }
            });
        }
@@ -1491,10 +1476,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                    Slog.w(TAG, "Failed to request wallpaper colors", e);
                }

                ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId);
                List<RectF> areas = mLocalColorRepo.getAreasByDisplayId(displayId);
                if (areas != null && areas.size() != 0) {
                    try {
                        connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas));
                        connector.mEngine.addLocalColorsAreas(areas);
                    } catch (RemoteException e) {
                        Slog.w(TAG, "Failed to register local colors areas", e);
                    }
@@ -2494,37 +2479,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
        }
        IWallpaperEngine engine = getEngine(which, userId, displayId);
        if (engine == null) return;
        ArrayList<RectF> validAreas = new ArrayList<>(regions.size());
        synchronized (mLock) {
            ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
            if (areas == null) areas = new ArraySet<>(regions.size());
            areas.addAll(regions);
            mLocalColorCallbackAreas.put(callback.asBinder(), areas);
        }
        for (int i = 0; i < regions.size(); i++) {
            if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) {
                continue;
            }
            RemoteCallbackList callbacks;
            synchronized (mLock) {
                callbacks = mLocalColorAreaCallbacks.get(
                        regions.get(i));
                if (callbacks == null) {
                    callbacks = new RemoteCallbackList();
                    mLocalColorAreaCallbacks.put(regions.get(i), callbacks);
                }
                mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId);
                ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
                if (displayAreas == null) {
                    displayAreas = new ArraySet<>(1);
                    mLocalColorDisplayIdAreas.put(displayId, displayAreas);
            mLocalColorRepo.addAreas(callback, regions, displayId);
        }
                displayAreas.add(regions.get(i));
            }
            validAreas.add(regions.get(i));
            callbacks.register(callback);
        }
        engine.addLocalColorsAreas(validAreas);
        engine.addLocalColorsAreas(regions);
    }

    @Override
@@ -2539,45 +2497,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
            throw new SecurityException("calling user id does not match");
        }
        final long identity = Binder.clearCallingIdentity();
        ArrayList<RectF> purgeAreas = new ArrayList<>();
        IBinder binder = callback.asBinder();
        List<RectF> purgeAreas = null;
        try {
            synchronized (mLock) {
                ArraySet<RectF> currentAreas = mLocalColorCallbackAreas.get(binder);
                if (currentAreas == null) return;
                currentAreas.removeAll(removeAreas);
                if (currentAreas.size() == 0) {
                    mLocalColorCallbackDisplayId.remove(binder);
                    for (RectF removeArea : removeAreas) {
                        RemoteCallbackList<ILocalWallpaperColorConsumer> remotes =
                                mLocalColorAreaCallbacks.get(removeArea);
                        if (remotes == null) continue;
                        remotes.unregister(callback);
                        if (remotes.getRegisteredCallbackCount() == 0) {
                            mLocalColorAreaCallbacks.remove(removeArea);
                            purgeAreas.add(removeArea);
                            ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
                            if (displayAreas != null) {
                                displayAreas.remove(removeArea);
                                if (displayAreas.size() == 0) {
                                    mLocalColorDisplayIdAreas.remove(displayId);
                                }
                            }
                        }
                purgeAreas = mLocalColorRepo.removeAreas(callback, removeAreas, displayId);
            }
                }
            }

        } catch (Exception e) {
            // ignore any exception
        } finally {
            Binder.restoreCallingIdentity(identity);
        }

        if (purgeAreas.size() == 0) return;
        IWallpaperEngine engine = getEngine(which, userId, displayId);
        if (engine == null) return;
        engine.removeLocalColorsAreas(purgeAreas);
        if (engine == null || purgeAreas == null) return;
        if (purgeAreas.size() > 0) engine.removeLocalColorsAreas(purgeAreas);
    }

    @Override
+135 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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 com.android.server.wallpaper;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

import static java.util.Arrays.asList;

import android.app.ILocalWallpaperColorConsumer;
import android.app.WallpaperColors;
import android.graphics.RectF;
import android.os.IBinder;

import androidx.test.runner.AndroidJUnit4;

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

import android.util.ArraySet;
import java.util.List;
import java.util.function.Consumer;


@RunWith(AndroidJUnit4.class)
public class LocalColorRepositoryTest {
    private LocalColorRepository mRepo = new LocalColorRepository();
    @Mock
    private IBinder mBinder1;
    @Mock
    private IBinder mBinder2;
    @Mock
    private ILocalWallpaperColorConsumer mCallback1;
    @Mock
    private ILocalWallpaperColorConsumer mCallback2;

    @Before
    public void setUp() {
        initMocks(this);
        when(mCallback1.asBinder()).thenReturn(mBinder1);
        when(mCallback2.asBinder()).thenReturn(mBinder2);
    }

    @Test
    public void testDisplayAreas() {
        RectF area1 = new RectF(1, 0, 0, 0);
        RectF area2 = new RectF(2, 1, 1, 1);
        ArraySet<RectF> expectedAreas = new ArraySet(asList(area1, area2));

        mRepo.addAreas(mCallback1, asList(area1), 0);
        mRepo.addAreas(mCallback2, asList(area2), 0);
        mRepo.addAreas(mCallback1, asList(new RectF(3, 1, 1, 1)), 1);

        assertEquals(expectedAreas, new ArraySet(mRepo.getAreasByDisplayId(0)));
        assertEquals(new ArraySet(asList(new RectF(3, 1, 1, 1))),
                new ArraySet(mRepo.getAreasByDisplayId(1)));
        assertEquals(new ArraySet(), new ArraySet(mRepo.getAreasByDisplayId(2)));
    }

    @Test
    public void testAddAndRemoveAreas() {
        RectF area1 = new RectF(1, 0, 0, 0);
        RectF area2 = new RectF(2, 1, 1, 1);

        mRepo.addAreas(mCallback1, asList(area1), 0);
        mRepo.addAreas(mCallback1, asList(area2), 0);
        mRepo.addAreas(mCallback2, asList(area2), 1);

        List<RectF> removed = mRepo.removeAreas(mCallback1, asList(area1), 0);
        assertEquals(new ArraySet(asList(area1)), new ArraySet(removed));
        // since we have another callback with a different area, we don't purge rid of any areas
        removed = mRepo.removeAreas(mCallback1, asList(area2), 0);
        assertEquals(new ArraySet(), new ArraySet(removed));
    }

    @Test
    public void testAreaCallback() {
        Consumer<ILocalWallpaperColorConsumer> consumer = mock(Consumer.class);
        WallpaperColors colors = mock(WallpaperColors.class);
        RectF area1 = new RectF(1, 0, 0, 0);
        RectF area2 = new RectF(2, 1, 1, 1);

        mRepo.addAreas(mCallback1, asList(area1), 0);
        mRepo.addAreas(mCallback1, asList(area2), 0);
        mRepo.addAreas(mCallback2, asList(area2), 0);

        mRepo.forEachCallback(consumer, area1, 0);
        Mockito.verify(consumer, times(1)).accept(eq(mCallback1));
        Mockito.verify(consumer, times(0)).accept(eq(mCallback2));
        mRepo.forEachCallback(consumer, area2, 0);
        Mockito.verify(consumer, times(2)).accept(eq(mCallback1));
        Mockito.verify(consumer, times(1)).accept(eq(mCallback2));
    }

    @Test
    public void unregisterCallbackWhenNoAreas() {
        RectF area1 = new RectF(1, 0, 0, 0);
        RectF area2 = new RectF(2, 1, 1, 1);

        assertFalse(mRepo.isCallbackAvailable(mCallback1));

        mRepo.addAreas(mCallback1, asList(area1), 0);
        mRepo.addAreas(mCallback1, asList(area2), 0);

        mRepo.removeAreas(mCallback1, asList(area1, area2), 0);
        assertFalse(mRepo.isCallbackAvailable(mCallback1));

        mRepo.addAreas(mCallback1, asList(area1), 0);
        assertTrue(mRepo.isCallbackAvailable(mCallback1));
    }
}