Merge "[Settings] Code refactor for BroadcastReceiver under Lifecycle" into tm-dev
diff --git a/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiver.java b/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiver.java
new file mode 100644
index 0000000..8aaa53e
--- /dev/null
+++ b/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiver.java
@@ -0,0 +1,104 @@
+/*
+ * 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.settings.network.helper;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.Lifecycle;
+import java.util.function.Consumer;
+
+/**
+ * A {@link BroadcastReceiver} for {@link Intent}.
+ *
+ * This is {@link BroadcastReceiver} supported by {@link LifecycleCallbackConverter},
+ * and only register when state is either START or RESUME.
+ */
+@VisibleForTesting
+public class LifecycleCallbackIntentReceiver extends LifecycleCallbackConverter<Intent> {
+    private static final String TAG = "LifecycleCallbackIntentReceiver";
+
+    @VisibleForTesting
+    protected final BroadcastReceiver mReceiver;
+
+    private final Runnable mRegisterCallback;
+    private final Runnable mUnRegisterCallback;
+
+    /**
+     * Constructor
+     * @param lifecycle {@link Lifecycle} to monitor
+     * @param context for this BroadcastReceiver
+     * @param filter the IntentFilter for BroadcastReceiver
+     * @param broadcastPermission for permission when listening
+     * @param scheduler for running in background thread
+     * @param resultCallback for the Intent from BroadcastReceiver
+     */
+    @VisibleForTesting
+    public LifecycleCallbackIntentReceiver(@NonNull Lifecycle lifecycle,
+            @NonNull Context context, @NonNull IntentFilter filter,
+            String broadcastPermission, Handler scheduler,
+            @NonNull Consumer<Intent> resultCallback) {
+        super(lifecycle, resultCallback);
+
+        // BroadcastReceiver
+        mReceiver = new BroadcastReceiver() {
+            public void onReceive(Context context, Intent intent) {
+                if (isInitialStickyBroadcast()) {
+                    return;
+                }
+                final String action = intent.getAction();
+                if ((action == null) || (action.length() <= 0)) {
+                    return;
+                }
+                postResult(intent);
+            }
+        };
+
+        // Register operation
+        mRegisterCallback = () -> {
+            Intent initIntent = context.registerReceiver(mReceiver,
+                    filter, broadcastPermission, scheduler);
+            if (initIntent != null) {
+                postResult(initIntent);
+            }
+        };
+
+        // Un-Register operation
+        mUnRegisterCallback = () -> {
+            context.unregisterReceiver(mReceiver);
+        };
+    }
+
+    @Override
+    public void setCallbackActive(boolean isActive) {
+        super.setCallbackActive(isActive);
+        Runnable op = (isActive) ? mRegisterCallback : mUnRegisterCallback;
+        op.run();
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        if (isCallbackActive()) {
+            setCallbackActive(false);
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiverTest.java b/tests/unit/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiverTest.java
new file mode 100644
index 0000000..c85937d
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiverTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.settings.network.helper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class LifecycleCallbackIntentReceiverTest implements LifecycleOwner {
+
+    private final LifecycleRegistry mRegistry = LifecycleRegistry.createUnsafe(this);
+
+    private static final String TEST_SCHEDULER_HANDLER = "testScheduler";
+    private static final String TEST_INTENT_ACTION = "testAction";
+    private static final String TEST_INTENT_PERMISSION = "testPermission";
+
+    private Context mContext;
+    private Intent mIntent;
+    private IntentFilter mIntentFilter;
+    private Handler mHandler;
+    private TestConsumer mConsumer;
+
+    private TestObj mTarget;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+
+        mIntentFilter = new IntentFilter(TEST_INTENT_ACTION);
+        mIntent = new Intent(TEST_INTENT_ACTION);
+
+        HandlerThread thread = new HandlerThread(TEST_SCHEDULER_HANDLER);
+        thread.start();
+
+        mHandler = new Handler(thread.getLooper());
+        mConsumer = new TestConsumer();
+
+        mTarget = new TestObj(getLifecycle(), mContext,
+                mIntentFilter, TEST_INTENT_PERMISSION,
+                mHandler, mConsumer);
+    }
+
+    public Lifecycle getLifecycle() {
+        return mRegistry;
+    }
+
+    @Test
+    public void receiver_register_whenActive() {
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+
+        assertThat(mTarget.getCallbackActiveCount(true)
+                + mTarget.getCallbackActiveCount(false)).isEqualTo(0);
+
+        mTarget.mReceiver.onReceive(mContext, mIntent);
+
+        assertThat(mConsumer.getCallbackCount()).isEqualTo(0);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mTarget.getCallbackActiveCount(true)).isEqualTo(1);
+        assertThat(mConsumer.getCallbackCount()).isEqualTo(0);
+
+        mTarget.mReceiver.onReceive(mContext, mIntent);
+
+        assertThat(mConsumer.getCallbackCount()).isEqualTo(1);
+        assertThat(mConsumer.getData()).isEqualTo(mIntent);
+    }
+
+    @Test
+    public void receiver_unregister_whenInActive() {
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        assertThat(mTarget.getCallbackActiveCount(false)).isEqualTo(1);
+
+        mTarget.mReceiver.onReceive(mContext, mIntent);
+
+        assertThat(mConsumer.getCallbackCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void receiver_register_whenReActive() {
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        assertThat(mTarget.getCallbackActiveCount(true)).isEqualTo(2);
+
+        mTarget.mReceiver.onReceive(mContext, mIntent);
+
+        assertThat(mConsumer.getCallbackCount()).isEqualTo(1);
+        assertThat(mConsumer.getData()).isEqualTo(mIntent);
+    }
+
+    @Test
+    public void receiver_close_whenDestroy() {
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        assertThat(mTarget.getCallbackActiveCount(false)).isEqualTo(1);
+
+        mTarget.mReceiver.onReceive(mContext, mIntent);
+
+        assertThat(mConsumer.getCallbackCount()).isEqualTo(0);
+    }
+
+    public static class TestConsumer implements Consumer<Intent> {
+        long mNumberOfCallback;
+        Intent mLatestData;
+
+        public TestConsumer() {}
+
+        public void accept(Intent data) {
+            mLatestData = data;
+            mNumberOfCallback ++;
+        }
+
+        protected long getCallbackCount() {
+            return mNumberOfCallback;
+        }
+
+        protected Intent getData() {
+            return mLatestData;
+        }
+    }
+
+    public static class TestObj extends LifecycleCallbackIntentReceiver {
+        long mCallbackActiveCount;
+        long mCallbackInActiveCount;
+
+        public TestObj(Lifecycle lifecycle, Context context, IntentFilter filter,
+                String broadcastPermission, Handler scheduler, Consumer<Intent> resultCallback) {
+            super(lifecycle, context, filter, broadcastPermission, scheduler, resultCallback);
+        }
+
+        @Override
+        public void setCallbackActive(boolean isActive) {
+            if (isActive) {
+                mCallbackActiveCount ++;
+            } else {
+                mCallbackInActiveCount ++;
+            }
+            super.setCallbackActive(isActive);
+        }
+
+        protected long getCallbackActiveCount(boolean forActive) {
+            return forActive ? mCallbackActiveCount : mCallbackInActiveCount;
+        }
+    }
+}