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

Commit 7b76289f authored by Chris Göllner's avatar Chris Göllner
Browse files

Extract SystemUIDialog.Delegate into DialogDelegate and make it generic

Test: AlertDialogWithDelegateTest.kt
Test: SystemUIDialogTest.java
Bug: 307193003
Flag: NONE
Change-Id: If3273bf3ee9a7c5236dbe0bedb611f2312609750
parent 78c77b18
Loading
Loading
Loading
Loading
+61 −0
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 com.android.systemui.statusbar.phone

import android.app.AlertDialog
import android.content.res.Configuration
import android.testing.TestableLooper.RunWithLooper
import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.util.mockito.mock
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito

@RunWith(AndroidJUnit4::class)
@RunWithLooper
@SmallTest
class AlertDialogWithDelegateTest : SysuiTestCase() {

    private val delegate = mock<DialogDelegate<AlertDialog>>()

    @Test
    fun delegateIsCalled_inCorrectOrder() {
        val configuration = Configuration()
        val inOrder = Mockito.inOrder(delegate)
        val dialog = createDialog()

        dialog.show()
        dialog.onWindowFocusChanged(/* hasFocus= */ true)
        dialog.onConfigurationChanged(configuration)
        dialog.dismiss()

        inOrder.verify(delegate).beforeCreate(dialog, /* savedInstanceState= */ null)
        inOrder.verify(delegate).onCreate(dialog, /* savedInstanceState= */ null)
        inOrder.verify(delegate).onStart(dialog)
        inOrder.verify(delegate).onWindowFocusChanged(dialog, /* hasFocus= */ true)
        inOrder.verify(delegate).onConfigurationChanged(dialog, configuration)
        inOrder.verify(delegate).onStop(dialog)
    }

    private fun createDialog(): AlertDialogWithDelegate =
        AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate).apply {
            window?.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
        }
}
+39 −0
Original line number Diff line number Diff line
@@ -29,22 +29,28 @@ import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.testing.TestableLooper.RunWithLooper;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.concurrent.atomic.AtomicBoolean;
@@ -58,6 +64,8 @@ public class SystemUIDialogTest extends SysuiTestCase {
    private FeatureFlags mFeatureFlags;
    @Mock
    private BroadcastDispatcher mBroadcastDispatcher;
    @Mock
    private DialogDelegate<SystemUIDialog> mDelegate;

    @Before
    public void setup() {
@@ -143,4 +151,35 @@ public class SystemUIDialogTest extends SysuiTestCase {
        assertThat(calledStart.get()).isTrue();
        assertThat(calledStop.get()).isTrue();
    }

    @Test
    public void delegateIsCalled_inCorrectOrder() {
        Configuration configuration = new Configuration();
        InOrder inOrder = Mockito.inOrder(mDelegate);
        SystemUIDialog dialog = createDialogWithDelegate();

        dialog.show();
        dialog.onWindowFocusChanged(/* hasFocus= */ true);
        dialog.onConfigurationChanged(configuration);
        dialog.dismiss();

        inOrder.verify(mDelegate).beforeCreate(dialog, /* savedInstanceState= */ null);
        inOrder.verify(mDelegate).onCreate(dialog, /* savedInstanceState= */ null);
        inOrder.verify(mDelegate).onStart(dialog);
        inOrder.verify(mDelegate).onWindowFocusChanged(dialog, /* hasFocus= */ true);
        inOrder.verify(mDelegate).onConfigurationChanged(dialog, configuration);
        inOrder.verify(mDelegate).onStop(dialog);
    }

    private SystemUIDialog createDialogWithDelegate() {
        SystemUIDialog.Factory factory = new SystemUIDialog.Factory(
                getContext(),
                mFeatureFlags,
                Dependency.get(SystemUIDialogManager.class),
                Dependency.get(SysUiState.class),
                Dependency.get(BroadcastDispatcher.class),
                Dependency.get(DialogLaunchAnimator.class)
        );
        return factory.create(mDelegate);
    }
}
+64 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.systemui.statusbar.phone

import android.app.AlertDialog
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.view.ViewRootImpl
import android.view.ViewRootImpl.ConfigChangedCallback
import androidx.annotation.StyleRes

/**
 * Implementation of [AlertDialog] that takes as parameter a [DialogDelegate].
 *
 * Can be used when composition is preferred over inheritance.
 */
class AlertDialogWithDelegate(
    context: Context,
    @StyleRes theme: Int,
    private val delegate: DialogDelegate<AlertDialog>
) : AlertDialog(context, theme), ConfigChangedCallback {

    override fun onCreate(savedInstanceState: Bundle?) {
        delegate.beforeCreate(dialog = this, savedInstanceState)
        super.onCreate(savedInstanceState)
        delegate.onCreate(dialog = this, savedInstanceState)
    }

    override fun onConfigurationChanged(configuration: Configuration) {
        delegate.onConfigurationChanged(dialog = this, configuration)
    }

    override fun onStart() {
        super.onStart()
        ViewRootImpl.addConfigCallback(this)
        delegate.onStart(dialog = this)
    }

    override fun onStop() {
        super.onStop()
        ViewRootImpl.removeConfigCallback(this)
        delegate.onStop(dialog = this)
    }

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        delegate.onWindowFocusChanged(dialog = this, hasFocus)
    }
}
+48 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.systemui.statusbar.phone

import android.app.Dialog
import android.content.res.Configuration
import android.os.Bundle
import android.view.ViewRootImpl

/**
 * A delegate class that should be implemented in place of subclassing [Dialog].
 *
 * To use with [SystemUIDialog], implement this interface and then pass an instance of your
 * implementation to [SystemUIDialog.Factory.create].
 */
interface DialogDelegate<T : Dialog> {
    /** Called before [Dialog.onCreate] is called. */
    fun beforeCreate(dialog: T, savedInstanceState: Bundle?) {}

    /** Called after [Dialog.onCreate] is called. */
    fun onCreate(dialog: T, savedInstanceState: Bundle?) {}

    /** Called after [Dialog.onStart] is called. */
    fun onStart(dialog: T) {}

    /** Called after [Dialog.onStop] is called. */
    fun onStop(dialog: T) {}

    /** Called after [Dialog.onWindowFocusChanged] is called. */
    fun onWindowFocusChanged(dialog: T, hasFocus: Boolean) {}

    /** Called as part of [ViewRootImpl.ConfigChangedCallback.onConfigurationChanged]. */
    fun onConfigurationChanged(dialog: T, configuration: Configuration) {}
}
+14 −49
Original line number Diff line number Diff line
@@ -42,13 +42,13 @@ import android.view.WindowManager.LayoutParams;
import androidx.annotation.Nullable;

import com.android.systemui.Dependency;
import com.android.systemui.res.R;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.util.DialogKt;

@@ -60,7 +60,7 @@ import javax.inject.Inject;
/**
 * Class for dialogs that should appear over panels and keyguard.
 *
 * DO NOT SUBCLASS THIS. See {@link SystemUIDialog.Delegate} for an interface that enables
 * DO NOT SUBCLASS THIS. See {@link DialogDelegate} for an interface that enables
 * customizing behavior via composition instead of inheritance. Clients should implement the
 * Delegate class and then pass their implementation into the SystemUIDialog constructor.
 *
@@ -79,7 +79,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh

    private final Context mContext;
    private final FeatureFlags mFeatureFlags;
    private final Delegate mDelegate;
    private final DialogDelegate<SystemUIDialog> mDelegate;
    @Nullable private final DismissReceiver mDismissReceiver;
    private final Handler mHandler = new Handler();
    private final SystemUIDialogManager mDialogManager;
@@ -110,7 +110,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
                Dependency.get(SysUiState.class),
                Dependency.get(BroadcastDispatcher.class),
                Dependency.get(DialogLaunchAnimator.class),
                new Delegate() {});
                new DialogDelegate<>() {});
    }

    @Inject
@@ -129,7 +129,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
                sysUiState,
                broadcastDispatcher,
                dialogLaunchAnimator,
                new Delegate(){});
                new DialogDelegate<>(){});
    }

    public static class Factory {
@@ -156,7 +156,11 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
            mDialogLaunchAnimator = dialogLaunchAnimator;
        }

        public SystemUIDialog create(Delegate delegate) {
        /**
         * Creates a new instance of {@link SystemUIDialog} with {@code delegate} as the {@link
         * DialogDelegate}.
         */
        public SystemUIDialog create(DialogDelegate<SystemUIDialog> delegate) {
            return new SystemUIDialog(
                    mContext,
                    DEFAULT_THEME,
@@ -188,7 +192,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
                sysUiState,
                broadcastDispatcher,
                dialogLaunchAnimator,
                new Delegate(){});
                new DialogDelegate<>(){});
    }

    public SystemUIDialog(
@@ -200,7 +204,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
            SysUiState sysUiState,
            BroadcastDispatcher broadcastDispatcher,
            DialogLaunchAnimator dialogLaunchAnimator,
            Delegate delegate) {
            DialogDelegate<SystemUIDialog> delegate) {
        super(context, theme);
        mContext = context;
        mFeatureFlags = featureFlags;
@@ -309,7 +313,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
     * should override this method instead.
     */
    protected void start() {
        mDelegate.start(this);
        mDelegate.onStart(this);
    }

    @Override
@@ -333,7 +337,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
     * should override this method instead.
     */
    protected void stop() {
        mDelegate.stop(this);
        mDelegate.onStop(this);
    }

    @Override
@@ -584,43 +588,4 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
            mDialog.dismiss();
        }
    }

    /**
     * A delegate class that should be implemented in place of sublcassing {@link SystemUIDialog}.
     *
     * Implement this interface and then pass an instance of your implementation to
     * {@link SystemUIDialog.Factory#create(Delegate)}.
     */
    public interface Delegate {
        /**
         * Called before {@link AlertDialog#onCreate} is called.
         */
        default void beforeCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}

        /**
         * Called after {@link AlertDialog#onCreate} is called.
         */
        default void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}

        /**
         * Called after {@link AlertDialog#onStart} is called.
         */
        default void start(SystemUIDialog dialog) {}

        /**
         * Called after {@link AlertDialog#onStop} is called.
         */
        default void stop(SystemUIDialog dialog) {}

        /**
         * Called after {@link AlertDialog#onWindowFocusChanged(boolean)} is called.
         */
        default void onWindowFocusChanged(SystemUIDialog dialog, boolean hasFocus) {}

        /**
         * Called as part of
         * {@link ViewRootImpl.ConfigChangedCallback#onConfigurationChanged(Configuration)}.
         */
        default void onConfigurationChanged(SystemUIDialog dialog, Configuration configuration) {}
    }
}