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

Commit 74f191c3 authored by Suphon Thanakornpakapong's avatar Suphon Thanakornpakapong
Browse files

Remove SettingsCache

parent e6c5af74
Loading
Loading
Loading
Loading
Loading
+18 −8
Original line number Diff line number Diff line
package foundation.e.blisslauncher;

import static foundation.e.blisslauncher.util.SettingsCache.NOTIFICATION_BADGING_URI;

import android.app.Application;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;

import foundation.e.blisslauncher.core.DeviceProfile;
import foundation.e.blisslauncher.core.IconsHandler;
@@ -13,9 +15,11 @@ import foundation.e.blisslauncher.core.blur.BlurWallpaperProvider;
import foundation.e.blisslauncher.core.customviews.WidgetHost;
import foundation.e.blisslauncher.features.launcher.AppProvider;
import foundation.e.blisslauncher.features.notification.NotificationService;
import foundation.e.blisslauncher.util.SettingsCache;

public class BlissLauncher extends Application {
    public static final Uri NOTIFICATION_BADGING_URI =
            Settings.Secure.getUriFor("notification_badging");

    private IconsHandler iconsPackHandler;
    private DeviceProfile deviceProfile;

@@ -35,13 +39,19 @@ public class BlissLauncher extends Application {
        connectAppProvider();
        BlurWallpaperProvider.Companion.getInstance(this);

        SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
        SettingsCache.OnChangeListener notificationLister = this::onNotificationSettingsChanged;
        settingsCache.register(NOTIFICATION_BADGING_URI, notificationLister);
        onNotificationSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
        ContentObserver notificationSettingsObserver = new ContentObserver(new Handler()) {
            @Override
            public void onChange(boolean selfChange) {
                onNotificationSettingsChanged();
            }
        };
        getContentResolver().registerContentObserver(
                NOTIFICATION_BADGING_URI, false, notificationSettingsObserver);
    }

    private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
    private void onNotificationSettingsChanged() {
        boolean areNotificationDotsEnabled = Settings.Secure.getInt(
                getContentResolver(), NOTIFICATION_BADGING_URI.getLastPathSegment(), 1) == 1;
        if (areNotificationDotsEnabled) {
            NotificationService.requestRebind(new ComponentName(
                    this, NotificationService.class));
+18 −13
Original line number Diff line number Diff line
package foundation.e.blisslauncher.features.notification;

import static foundation.e.blisslauncher.util.SettingsCache.NOTIFICATION_BADGING_URI;
import static foundation.e.blisslauncher.BlissLauncher.NOTIFICATION_BADGING_URI;

import android.content.Intent;
import android.database.ContentObserver;
import android.os.Handler;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;

import java.util.Collections;

import foundation.e.blisslauncher.core.utils.ListUtil;
import foundation.e.blisslauncher.util.SettingsCache;

/**
 * Created by falcon on 14/3/18.
@@ -22,31 +24,34 @@ public class NotificationService extends NotificationListenerService {
    NotificationRepository mNotificationRepository;

    private boolean mAreDotsEnabled;
    private SettingsCache mSettingsCache;
    private SettingsCache.OnChangeListener mNotificationSettingsChangedListener;
    private final ContentObserver mNotificationSettingsObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            onNotificationSettingsChanged();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mNotificationRepository = NotificationRepository.getNotificationRepository();

        mSettingsCache = SettingsCache.INSTANCE.get(this);
        mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
        mSettingsCache.register(NOTIFICATION_BADGING_URI,
                mNotificationSettingsChangedListener);
        onNotificationSettingsChanged(mSettingsCache.getValue(NOTIFICATION_BADGING_URI));
        getContentResolver().registerContentObserver(
                NOTIFICATION_BADGING_URI, false, mNotificationSettingsObserver);
        onNotificationSettingsChanged();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener);
        getContentResolver().unregisterContentObserver(mNotificationSettingsObserver);
        mNotificationRepository.updateNotification(Collections.emptyList());
    }

    private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
        mAreDotsEnabled = areNotificationDotsEnabled;
        if (!areNotificationDotsEnabled && sIsConnected) {
    private void onNotificationSettingsChanged() {
        mAreDotsEnabled = Settings.Secure.getInt(
                getContentResolver(), NOTIFICATION_BADGING_URI.getLastPathSegment(), 1) == 1;
        if (!mAreDotsEnabled && sIsConnected) {
            requestUnbind();
            updateNotifications();
        }
+0 −118
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 foundation.e.blisslauncher.util;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;

import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Extension of {@link AbstractExecutorService} which executed on a provided looper.
 */
public class LooperExecutor extends AbstractExecutorService {

    private final Handler mHandler;

    public LooperExecutor(Looper looper) {
        mHandler = new Handler(looper);
    }

    public Handler getHandler() {
        return mHandler;
    }

    @Override
    public void execute(Runnable runnable) {
        if (getHandler().getLooper() == Looper.myLooper()) {
            runnable.run();
        } else {
            getHandler().post(runnable);
        }
    }

    /**
     * Same as execute, but never runs the action inline.
     */
    public void post(Runnable runnable) {
        getHandler().post(runnable);
    }

    /**
     * Not supported and throws an exception when used.
     */
    @Override
    @Deprecated
    public void shutdown() {
        throw new UnsupportedOperationException();
    }

    /**
     * Not supported and throws an exception when used.
     */
    @Override
    @Deprecated
    public List<Runnable> shutdownNow() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isShutdown() {
        return false;
    }

    @Override
    public boolean isTerminated() {
        return false;
    }

    /**
     * Not supported and throws an exception when used.
     */
    @Override
    @Deprecated
    public boolean awaitTermination(long l, TimeUnit timeUnit) {
        throw new UnsupportedOperationException();
    }

    /**
     * Returns the thread for this executor
     */
    public Thread getThread() {
        return getHandler().getLooper().getThread();
    }

    /**
     * Returns the looper for this executor
     */
    public Looper getLooper() {
        return getHandler().getLooper();
    }

    /**
     * Set the priority of a thread, based on Linux priorities.
     * @param priority Linux priority level, from -20 for highest scheduling priority
     *                to 19 for lowest scheduling priority.
     * @see Process#setThreadPriority(int, int)
     */
    public void setThreadPriority(int priority) {
        Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority);
    }
}
+0 −66
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 * Copyright 2022 ECORP SAS
 *
 * 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.
 *
 * Modifications copyright 2021, Lawnchair
 */
package foundation.e.blisslauncher.util;

import android.content.Context;
import android.os.Looper;

import java.util.concurrent.ExecutionException;

/**
 * Utility class for defining singletons which are initiated on main thread.
 */
public class MainThreadInitializedObject<T> {

    private static final LooperExecutor MAIN_EXECUTOR = new LooperExecutor(Looper.getMainLooper());

    private final ObjectProvider<T> mProvider;
    private T mValue;

    public MainThreadInitializedObject(ObjectProvider<T> provider) {
        mProvider = provider;
    }

    public T get(Context context) {
        if (mValue == null) {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                mValue = mProvider.get(context.getApplicationContext());
                onPostInit(context);
            } else {
                try {
                    return MAIN_EXECUTOR.submit(() -> get(context)).get();
                } catch (InterruptedException|ExecutionException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return mValue;
    }

    protected void onPostInit(Context context) { }

    public T getNoCreate() {
        return mValue;
    }

    public interface ObjectProvider<T> {

        T get(Context context);
    }
}
+0 −179
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 foundation.e.blisslauncher.util;

import static android.provider.Settings.System.ACCELEROMETER_ROTATION;

import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;

import androidx.annotation.VisibleForTesting;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * ContentObserver over Settings keys that also has a caching layer.
 * Consumers can register for callbacks via {@link #register(Uri, OnChangeListener)} and
 * {@link #unregister(Uri, OnChangeListener)} methods.
 *
 * This can be used as a normal cache without any listeners as well via the
 * {@link #getValue(Uri, int)} and {@link #onChange)} to update (and subsequently call
 * get)
 *
 * The cache will be invalidated/updated through the normal
 * {@link ContentObserver#onChange(boolean)} calls
 *
 * Cache will also be updated if a key queried is missing (even if it has no listeners registered).
 */
public class SettingsCache extends ContentObserver implements AutoCloseable {

    /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
    public static final Uri NOTIFICATION_BADGING_URI =
            Settings.Secure.getUriFor("notification_badging");
    /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
    public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
    /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
    public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
            "swipe_bottom_to_notification_enabled";
    public static final Uri ROTATION_SETTING_URI =
            Settings.System.getUriFor(ACCELEROMETER_ROTATION);

    private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();

    /**
     * Caches the last seen value for registered keys.
     */
    private Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
    private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>();
    protected final ContentResolver mResolver;

    /**
     * Singleton instance
     */
    public static MainThreadInitializedObject<SettingsCache> INSTANCE =
            new MainThreadInitializedObject<>(SettingsCache::new);

    private SettingsCache(Context context) {
        super(new Handler());
        mResolver = context.getContentResolver();
    }

    @Override
    public void close() {
        mResolver.unregisterContentObserver(this);
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        // We use default of 1, but if we're getting an onChange call, can assume a non-default
        // value will exist
        boolean newVal = updateValue(uri, 1 /* Effectively Unused */);
        if (!mListenerMap.containsKey(uri)) {
            return;
        }

        for (OnChangeListener listener : mListenerMap.get(uri)) {
            listener.onSettingsChanged(newVal);
        }
    }

    /**
     * Returns the value for this classes key from the cache. If not in cache, will call
     * {@link #updateValue(Uri, int)} to fetch.
     */
    public boolean getValue(Uri keySetting) {
        return getValue(keySetting, 1);
    }

    /**
     * Returns the value for this classes key from the cache. If not in cache, will call
     * {@link #updateValue(Uri, int)} to fetch.
     */
    public boolean getValue(Uri keySetting, int defaultValue) {
        if (mKeyCache.containsKey(keySetting)) {
            return mKeyCache.get(keySetting);
        } else {
            return updateValue(keySetting, defaultValue);
        }
    }

    /**
     * Does not de-dupe if you add same listeners for the same key multiple times.
     * Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
     */
    public void register(Uri uri, OnChangeListener changeListener) {
        if (mListenerMap.containsKey(uri)) {
            mListenerMap.get(uri).add(changeListener);
        } else {
            CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>();
            l.add(changeListener);
            mListenerMap.put(uri, l);
            mResolver.registerContentObserver(uri, false, this);
        }
    }

    private boolean updateValue(Uri keyUri, int defaultValue) {
        String key = keyUri.getLastPathSegment();
        boolean newVal;
        if (keyUri.toString().startsWith(SYSTEM_URI_PREFIX)) {
            newVal = Settings.System.getInt(mResolver, key, defaultValue) == 1;
        } else { // SETTING_SECURE
            newVal = Settings.Secure.getInt(mResolver, key, defaultValue) == 1;
        }

        mKeyCache.put(keyUri, newVal);
        return newVal;
    }

    /**
     * Call to stop receiving updates on the given {@param listener}.
     * This Uri/Listener pair must correspond to the same pair called with for
     * {@link #register(Uri, OnChangeListener)}
     */
    public void unregister(Uri uri, OnChangeListener listener) {
        List<OnChangeListener> listenersToRemoveFrom = mListenerMap.get(uri);
        if (!listenersToRemoveFrom.contains(listener)) {
            return;
        }

        listenersToRemoveFrom.remove(listener);
        if (listenersToRemoveFrom.isEmpty()) {
            mListenerMap.remove(uri);
        }
    }

    /**
     * Don't use this. Ever.
     * @param keyCache Cache to replace {@link #mKeyCache}
     */
    @VisibleForTesting
    void setKeyCache(Map<Uri, Boolean> keyCache) {
        mKeyCache = keyCache;
    }

    public interface OnChangeListener {
        void onSettingsChanged(boolean isEnabled);
    }
}