[Android] Move PrefChangeRegistrar to components/prefs

Make it possible to observe local_state instead of Profile prefs.

Bug: 381122436,40132529
Change-Id: Ib7ec2cfac791505b89586df7d682de40bdba2149
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6055147
Reviewed-by: Theresa Sullivan <[email protected]>
Reviewed-by: Ted Choc <[email protected]>
Commit-Queue: Henrique Nakashima <[email protected]>
Reviewed-by: Yaron Friedman <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1390455}
diff --git a/components/prefs/BUILD.gn b/components/prefs/BUILD.gn
index e31c110f..9deb408f 100644
--- a/components/prefs/BUILD.gn
+++ b/components/prefs/BUILD.gn
@@ -64,6 +64,8 @@
 
   if (is_android) {
     sources += [
+      "android/pref_change_registrar_android.cc",
+      "android/pref_change_registrar_android.h",
       "android/pref_service_android.cc",
       "android/pref_service_android.h",
     ]
diff --git a/components/prefs/android/BUILD.gn b/components/prefs/android/BUILD.gn
index ff3c055..37bda57f 100644
--- a/components/prefs/android/BUILD.gn
+++ b/components/prefs/android/BUILD.gn
@@ -6,13 +6,20 @@
 import("//third_party/jni_zero/jni_zero.gni")
 
 generate_jni("jni_headers") {
-  sources = [ "java/src/org/chromium/components/prefs/PrefService.java" ]
+  sources = [
+    "java/src/org/chromium/components/prefs/PrefChangeRegistrar.java",
+    "java/src/org/chromium/components/prefs/PrefService.java",
+  ]
 }
 
 android_library("java") {
   srcjar_deps = [ ":jni_headers" ]
-  sources = [ "java/src/org/chromium/components/prefs/PrefService.java" ]
+  sources = [
+    "java/src/org/chromium/components/prefs/PrefChangeRegistrar.java",
+    "java/src/org/chromium/components/prefs/PrefService.java",
+  ]
   deps = [
+    "//base:base_java",
     "//build/android:build_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/jni_zero:jni_zero_java",
diff --git a/components/prefs/android/java/src/org/chromium/components/prefs/PrefChangeRegistrar.java b/components/prefs/android/java/src/org/chromium/components/prefs/PrefChangeRegistrar.java
new file mode 100644
index 0000000..9f0f02f53
--- /dev/null
+++ b/components/prefs/android/java/src/org/chromium/components/prefs/PrefChangeRegistrar.java
@@ -0,0 +1,94 @@
+// Copyright 2017 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.prefs;
+
+import android.util.ArrayMap;
+
+import org.jni_zero.CalledByNative;
+import org.jni_zero.JniType;
+import org.jni_zero.NativeMethods;
+
+import java.util.Map;
+
+/**
+ * This class is the Java implementation of native PrefChangeRegistrar. It receives notification for
+ * changes of one or more preferences from native PrefService.
+ *
+ * <p>Note that {@link #destroy} should be called to destroy the native PrefChangeRegistrar.
+ */
+public class PrefChangeRegistrar {
+    /** Interface for callback when registered preference is changed. */
+    public interface PrefObserver {
+        void onPreferenceChange();
+    }
+
+    /** Mapping preference key and corresponding observer. */
+    private final Map<String, PrefObserver> mObservers = new ArrayMap<>();
+
+    /** Native pointer for PrefChangeRegistrarAndroid. */
+    private long mNativeRegistrar;
+
+    /** Initialize native PrefChangeRegistrar. */
+    public PrefChangeRegistrar(PrefService prefs) {
+        mNativeRegistrar = PrefChangeRegistrarJni.get().init(PrefChangeRegistrar.this, prefs);
+    }
+
+    /**
+     * Add an observer to be notified of changes to the specified preference.
+     *
+     * @param preference The preference to be observed.
+     * @param observer The observer to be notified.
+     */
+    public void addObserver(String preference, PrefObserver observer) {
+        assert mObservers.get(preference) == null
+                : "Only one observer should be added to each preference.";
+        mObservers.put(preference, observer);
+        PrefChangeRegistrarJni.get().add(mNativeRegistrar, PrefChangeRegistrar.this, preference);
+    }
+
+    /**
+     * Remove an observer for the specified preference if it has previously been added.
+     *
+     * @param preference The specified preference.
+     */
+    public void removeObserver(String preference) {
+        PrefObserver observer = mObservers.get(preference);
+        if (observer == null) return;
+        mObservers.remove(preference);
+        PrefChangeRegistrarJni.get().remove(mNativeRegistrar, PrefChangeRegistrar.this, preference);
+    }
+
+    /** Destroy native PrefChangeRegistrar. */
+    public void destroy() {
+        if (mNativeRegistrar != 0) {
+            PrefChangeRegistrarJni.get().destroy(mNativeRegistrar, PrefChangeRegistrar.this);
+        }
+        mNativeRegistrar = 0;
+    }
+
+    @CalledByNative
+    private void onPreferenceChange(String preference) {
+        assert mObservers.get(preference) != null
+                : "Notification from unregistered preference changes.";
+        mObservers.get(preference).onPreferenceChange();
+    }
+
+    @NativeMethods
+    public interface Natives {
+        long init(PrefChangeRegistrar caller, @JniType("PrefService*") PrefService prefService);
+
+        void add(
+                long nativePrefChangeRegistrarAndroid,
+                PrefChangeRegistrar caller,
+                String preference);
+
+        void remove(
+                long nativePrefChangeRegistrarAndroid,
+                PrefChangeRegistrar caller,
+                String preference);
+
+        void destroy(long nativePrefChangeRegistrarAndroid, PrefChangeRegistrar caller);
+    }
+}
diff --git a/components/prefs/android/pref_change_registrar_android.cc b/components/prefs/android/pref_change_registrar_android.cc
new file mode 100644
index 0000000..c94d45b5
--- /dev/null
+++ b/components/prefs/android/pref_change_registrar_android.cc
@@ -0,0 +1,66 @@
+// Copyright 2017 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/prefs/android/pref_change_registrar_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/functional/bind.h"
+#include "components/prefs/android/pref_service_android.h"
+
+// Must come after all headers that specialize FromJniType() / ToJniType().
+#include "components/prefs/android/jni_headers/PrefChangeRegistrar_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+
+PrefChangeRegistrarAndroid::PrefChangeRegistrarAndroid(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    PrefService* prefs) {
+  pref_change_registrar_.Init(prefs);
+  pref_change_registrar_jobject_.Reset(env, obj);
+}
+
+PrefChangeRegistrarAndroid::~PrefChangeRegistrarAndroid() = default;
+
+void PrefChangeRegistrarAndroid::Destroy(JNIEnv*,
+                                         const JavaParamRef<jobject>&) {
+  delete this;
+}
+
+void PrefChangeRegistrarAndroid::Add(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jstring>& j_preference) {
+  std::string preference =
+      base::android::ConvertJavaStringToUTF8(env, j_preference);
+  pref_change_registrar_.Add(
+      preference,
+      base::BindRepeating(&PrefChangeRegistrarAndroid::OnPreferenceChange,
+                          base::Unretained(this), preference));
+}
+
+void PrefChangeRegistrarAndroid::Remove(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const JavaParamRef<jstring>& j_preference) {
+  pref_change_registrar_.Remove(
+      base::android::ConvertJavaStringToUTF8(env, j_preference));
+}
+
+void PrefChangeRegistrarAndroid::OnPreferenceChange(std::string preference) {
+  JNIEnv* env = AttachCurrentThread();
+  Java_PrefChangeRegistrar_onPreferenceChange(
+      env, pref_change_registrar_jobject_,
+      base::android::ConvertUTF8ToJavaString(env, preference));
+}
+
+jlong JNI_PrefChangeRegistrar_Init(JNIEnv* env,
+                                   const JavaParamRef<jobject>& obj,
+                                   PrefService* prefs) {
+  return reinterpret_cast<intptr_t>(
+      new PrefChangeRegistrarAndroid(env, obj, prefs));
+}
diff --git a/components/prefs/android/pref_change_registrar_android.h b/components/prefs/android/pref_change_registrar_android.h
new file mode 100644
index 0000000..53f780df
--- /dev/null
+++ b/components/prefs/android/pref_change_registrar_android.h
@@ -0,0 +1,45 @@
+// Copyright 2017 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREFS_ANDROID_PREF_CHANGE_REGISTRAR_ANDROID_H_
+#define COMPONENTS_PREFS_ANDROID_PREF_CHANGE_REGISTRAR_ANDROID_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/raw_ptr.h"
+#include "components/prefs/pref_change_registrar.h"
+
+using base::android::JavaParamRef;
+using base::android::ScopedJavaGlobalRef;
+
+class PrefService;
+
+// This class contains a PrefChangeRegistrar that observes PrefService changes
+// for Android.
+class PrefChangeRegistrarAndroid {
+ public:
+  PrefChangeRegistrarAndroid(JNIEnv* env,
+                             const JavaParamRef<jobject>& obj,
+                             PrefService* prefs);
+  void Destroy(JNIEnv*, const JavaParamRef<jobject>&);
+
+  PrefChangeRegistrarAndroid(const PrefChangeRegistrarAndroid&) = delete;
+  PrefChangeRegistrarAndroid& operator=(const PrefChangeRegistrarAndroid&) =
+      delete;
+
+  void Add(JNIEnv* env,
+           const JavaParamRef<jobject>& obj,
+           const JavaParamRef<jstring>& j_preference);
+  void Remove(JNIEnv* env,
+              const JavaParamRef<jobject>& obj,
+              const JavaParamRef<jstring>& j_preference);
+
+ private:
+  ~PrefChangeRegistrarAndroid();
+  void OnPreferenceChange(std::string preference);
+
+  PrefChangeRegistrar pref_change_registrar_;
+  ScopedJavaGlobalRef<jobject> pref_change_registrar_jobject_;
+};
+
+#endif  // COMPONENTS_PREFS_ANDROID_PREF_CHANGE_REGISTRAR_ANDROID_H_