From c2030898ef2540d5619c66d5fd4066f2267712bd Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Tue, 19 Apr 2022 23:04:13 +0800 Subject: [PATCH] [Settings] Code refactor for BroadcastReceiver under Lifecycle This is an implementation of BroadcastReceiver which supported by LifecycleCallbackConverter. Registration of BroadcastReceiver only take place when Lifecycle in RESUME status. Bug: 229689535 Test: unit test Change-Id: Ia2af82d5cbb391034627e5259a9e0c8683a0c5a1 --- .../LifecycleCallbackIntentReceiver.java | 104 ++++++++++ .../LifecycleCallbackIntentReceiverTest.java | 184 ++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 src/com/android/settings/network/helper/LifecycleCallbackIntentReceiver.java create mode 100644 tests/unit/src/com/android/settings/network/helper/LifecycleCallbackIntentReceiverTest.java 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 00000000000..8aaa53e0af8 --- /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 { + 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 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 00000000000..c85937d3a34 --- /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 { + 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 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; + } + } +}