blob: 33e7a7621340959120051662a9e6eabf6c92cf0f [file] [log] [blame]
/*
* Copyright (C) 2022 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.inputmethod;
import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import android.annotation.AnyThread;
import android.annotation.BoolRes;
import android.annotation.NonNull;
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.PatternMatcher;
import android.util.Slog;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
* A wrapper object for any boolean resource defined in {@code "android"} package, in a way that is
* aware of per-user Runtime Resource Overlay (RRO).
*/
final class OverlayableSystemBooleanResourceWrapper implements AutoCloseable {
private static final String TAG = "OverlayableSystemBooleanResourceWrapper";
private static final String SYSTEM_PACKAGE_NAME = "android";
@UserIdInt
private final int mUserId;
@NonNull
private final AtomicBoolean mValueRef;
@NonNull
private final AtomicReference<Runnable> mCleanerRef;
/**
* Creates {@link OverlayableSystemBooleanResourceWrapper} for the given boolean resource ID
* with a value change callback for the user associated with the {@link Context}.
*
* @param userContext The {@link Context} to be used to access the resource. This needs to be
* associated with the right user because the Runtime Resource Overlay (RRO)
* is per-user configuration.
* @param boolResId The resource ID to be queried.
* @param handler {@link Handler} to be used to dispatch {@code callback}.
* @param callback The callback to be notified when the specified value might be updated.
* The callback needs to take care of spurious wakeup. The value returned from
* {@link #get()} may look to be exactly the same as the previously read value
* e.g. when the value is changed from {@code false} to {@code true} to
* {@code false} in a very short period of time, because {@link #get()} always
* does volatile-read.
* @return New {@link OverlayableSystemBooleanResourceWrapper}.
*/
@NonNull
@UserHandleAware
static OverlayableSystemBooleanResourceWrapper create(@NonNull Context userContext,
@BoolRes int boolResId, @NonNull Handler handler,
@NonNull Consumer<OverlayableSystemBooleanResourceWrapper> callback) {
// Note that we cannot fully trust this initial value due to the dead time between obtaining
// the value here and setting up a broadcast receiver for change callback below.
// We will refresh the value again later after setting up the change callback anyway.
final AtomicBoolean valueRef = new AtomicBoolean(evaluate(userContext, boolResId));
final AtomicReference<Runnable> cleanerRef = new AtomicReference<>();
final OverlayableSystemBooleanResourceWrapper object =
new OverlayableSystemBooleanResourceWrapper(userContext.getUserId(), valueRef,
cleanerRef);
final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final boolean newValue = evaluate(userContext, boolResId);
if (newValue != valueRef.getAndSet(newValue)) {
callback.accept(object);
}
}
};
userContext.registerReceiver(broadcastReceiver, intentFilter,
null /* broadcastPermission */, handler,
Context.RECEIVER_NOT_EXPORTED);
cleanerRef.set(() -> userContext.unregisterReceiver(broadcastReceiver));
// Make sure that the initial observable value is obtained after the change callback is set.
valueRef.set(evaluate(userContext, boolResId));
return object;
}
private OverlayableSystemBooleanResourceWrapper(@UserIdInt int userId,
@NonNull AtomicBoolean valueRef, @NonNull AtomicReference<Runnable> cleanerRef) {
mUserId = userId;
mValueRef = valueRef;
mCleanerRef = cleanerRef;
}
/**
* @return The boolean resource value.
*/
@AnyThread
boolean get() {
return mValueRef.get();
}
/**
* @return The user ID associated with this resource reader.
*/
@AnyThread
@UserIdInt
int getUserId() {
return mUserId;
}
@AnyThread
private static boolean evaluate(@NonNull Context context, @BoolRes int boolResId) {
try {
return context.getPackageManager()
.getResourcesForApplication(SYSTEM_PACKAGE_NAME)
.getBoolean(boolResId);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed", e);
return false;
}
}
/**
* Cleans up the callback.
*/
@AnyThread
@Override
public void close() {
final Runnable cleaner = mCleanerRef.getAndSet(null);
if (cleaner != null) {
cleaner.run();
}
}
}