From d637dd37cb39e4c46acd63f974b626d4a16d53ae Mon Sep 17 00:00:00 2001 From: Nicolo' Mazzucato Date: Wed, 6 Nov 2024 12:51:52 +0000 Subject: Update keyguard presentation logic to take into account shade position Instead of showing the keyguard presentation on all "non default display", it will be shown in all "displays without shade windows" (+other pre-existing conditions are kept) The proper implementation of ShadePositionRepository is in progress in another cl. KeyguardDisplayManagerTest has been converted to kotlin to make it slighly easier to deal with flow testing. Bug: 362719719 Test: KeyguardDisplayManagerTest Flag: com.android.systemui.shade_window_goes_around Change-Id: Ie1bc93ca65b71199988ccb1ffdf76b94a1ec01e9 --- .../keyguard/KeyguardDisplayManagerTest.java | 152 -------------- .../android/keyguard/KeyguardDisplayManagerTest.kt | 225 +++++++++++++++++++++ .../android/keyguard/KeyguardDisplayManager.java | 42 +++- .../systemui/shade/ShadeDisplayAwareModule.kt | 1 - .../data/repository/FakeShadePositionRepository.kt | 36 ++++ .../shade/shared/flag/ShadeWindowGoesAround.kt | 2 +- 6 files changed, 300 insertions(+), 158 deletions(-) delete mode 100644 packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java create mode 100644 packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt create mode 100644 packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java deleted file mode 100644 index dd58ea7db2bc..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2021 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.keyguard; - -import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.hardware.display.DisplayManagerGlobal; -import android.testing.TestableLooper; -import android.view.Display; -import android.view.DisplayInfo; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.navigationbar.NavigationBarController; -import com.android.systemui.settings.FakeDisplayTracker; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.concurrent.Executor; - -@SmallTest -@RunWith(AndroidJUnit4.class) -@TestableLooper.RunWithLooper -public class KeyguardDisplayManagerTest extends SysuiTestCase { - - @Mock - private NavigationBarController mNavigationBarController; - @Mock - private ConnectedDisplayKeyguardPresentation.Factory - mConnectedDisplayKeyguardPresentationFactory; - @Mock - private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation; - @Mock - private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper; - @Mock - private KeyguardStateController mKeyguardStateController; - - private Executor mMainExecutor = Runnable::run; - private Executor mBackgroundExecutor = Runnable::run; - private KeyguardDisplayManager mManager; - private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); - // The default and secondary displays are both in the default group - private Display mDefaultDisplay; - private Display mSecondaryDisplay; - - // This display is in a different group from the default and secondary displays. - private Display mAlwaysUnlockedDisplay; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController, - mDisplayTracker, mMainExecutor, mBackgroundExecutor, mDeviceStateHelper, - mKeyguardStateController, mConnectedDisplayKeyguardPresentationFactory)); - doReturn(mConnectedDisplayKeyguardPresentation).when( - mConnectedDisplayKeyguardPresentationFactory).create(any()); - doReturn(mConnectedDisplayKeyguardPresentation).when(mManager) - .createPresentation(any()); - mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY, - new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); - mSecondaryDisplay = new Display(DisplayManagerGlobal.getInstance(), - Display.DEFAULT_DISPLAY + 1, - new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); - - DisplayInfo alwaysUnlockedDisplayInfo = new DisplayInfo(); - alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2; - alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED; - mAlwaysUnlockedDisplay = new Display(DisplayManagerGlobal.getInstance(), - Display.DEFAULT_DISPLAY, - alwaysUnlockedDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); - } - - @Test - public void testShow_defaultDisplayOnly() { - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay}); - mManager.show(); - verify(mManager, never()).createPresentation(any()); - } - - @Test - public void testShow_includeSecondaryDisplay() { - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay}); - mManager.show(); - verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay)); - } - - @Test - public void testShow_includeAlwaysUnlockedDisplay() { - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay}); - - mManager.show(); - verify(mManager, never()).createPresentation(any()); - } - - @Test - public void testShow_includeSecondaryAndAlwaysUnlockedDisplays() { - mDisplayTracker.setAllDisplays( - new Display[]{mDefaultDisplay, mSecondaryDisplay, mAlwaysUnlockedDisplay}); - - mManager.show(); - verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay)); - } - - @Test - public void testShow_concurrentDisplayActive_occluded() { - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay}); - - when(mDeviceStateHelper.isConcurrentDisplayActive(mSecondaryDisplay)).thenReturn(true); - when(mKeyguardStateController.isOccluded()).thenReturn(true); - verify(mManager, never()).createPresentation(eq(mSecondaryDisplay)); - } - - @Test - public void testShow_presentationCreated() { - when(mManager.createPresentation(any())).thenCallRealMethod(); - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay}); - - mManager.show(); - - verify(mConnectedDisplayKeyguardPresentationFactory).create(eq(mSecondaryDisplay)); - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt new file mode 100644 index 000000000000..57a67973f34f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2024 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.keyguard + +import android.hardware.display.DisplayManagerGlobal +import android.platform.test.annotations.EnableFlags +import android.testing.TestableLooper.RunWithLooper +import android.view.Display +import android.view.DisplayAdjustments +import android.view.DisplayInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardDisplayManager.DeviceStateHelper +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.navigationbar.NavigationBarController +import com.android.systemui.settings.FakeDisplayTracker +import com.android.systemui.shade.data.repository.FakeShadePositionRepository +import com.android.systemui.statusbar.policy.KeyguardStateController +import java.util.concurrent.Executor +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.reset +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper +@OptIn(ExperimentalCoroutinesApi::class) +class KeyguardDisplayManagerTest : SysuiTestCase() { + @Mock private val navigationBarController = mock(NavigationBarController::class.java) + @Mock + private val presentationFactory = mock(ConnectedDisplayKeyguardPresentation.Factory::class.java) + @Mock + private val connectedDisplayKeyguardPresentation = + mock(ConnectedDisplayKeyguardPresentation::class.java) + @Mock private val deviceStateHelper = mock(DeviceStateHelper::class.java) + @Mock private val keyguardStateController = mock(KeyguardStateController::class.java) + private val shadePositionRepository = FakeShadePositionRepository() + + private val mainExecutor = Executor { it.run() } + private val backgroundExecutor = Executor { it.run() } + private lateinit var manager: KeyguardDisplayManager + private val displayTracker = FakeDisplayTracker(mContext) + // The default and secondary displays are both in the default group + private lateinit var defaultDisplay: Display + private lateinit var secondaryDisplay: Display + + private val testScope = TestScope(UnconfinedTestDispatcher()) + + // This display is in a different group from the default and secondary displays. + private lateinit var alwaysUnlockedDisplay: Display + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + manager = + KeyguardDisplayManager( + mContext, + { navigationBarController }, + displayTracker, + mainExecutor, + backgroundExecutor, + deviceStateHelper, + keyguardStateController, + presentationFactory, + { shadePositionRepository }, + testScope.backgroundScope, + ) + whenever(presentationFactory.create(any())).doReturn(connectedDisplayKeyguardPresentation) + + defaultDisplay = + Display( + DisplayManagerGlobal.getInstance(), + Display.DEFAULT_DISPLAY, + DisplayInfo(), + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS, + ) + secondaryDisplay = + Display( + DisplayManagerGlobal.getInstance(), + Display.DEFAULT_DISPLAY + 1, + DisplayInfo(), + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS, + ) + + val alwaysUnlockedDisplayInfo = DisplayInfo() + alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2 + alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED + alwaysUnlockedDisplay = + Display( + DisplayManagerGlobal.getInstance(), + Display.DEFAULT_DISPLAY, + alwaysUnlockedDisplayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS, + ) + } + + @Test + fun testShow_defaultDisplayOnly() { + displayTracker.allDisplays = arrayOf(defaultDisplay) + manager.show() + verify(presentationFactory, never()).create(any()) + } + + @Test + fun testShow_includeSecondaryDisplay() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + manager.show() + verify(presentationFactory).create(eq(secondaryDisplay)) + } + + @Test + fun testShow_includeAlwaysUnlockedDisplay() { + displayTracker.allDisplays = arrayOf(defaultDisplay, alwaysUnlockedDisplay) + + manager.show() + verify(presentationFactory, never()).create(any()) + } + + @Test + fun testShow_includeSecondaryAndAlwaysUnlockedDisplays() { + displayTracker.allDisplays = + arrayOf(defaultDisplay, secondaryDisplay, alwaysUnlockedDisplay) + + manager.show() + verify(presentationFactory).create(eq(secondaryDisplay)) + } + + @Test + fun testShow_concurrentDisplayActive_occluded() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + + whenever(deviceStateHelper.isConcurrentDisplayActive(secondaryDisplay)).thenReturn(true) + whenever(keyguardStateController.isOccluded).thenReturn(true) + verify(presentationFactory, never()).create(eq(secondaryDisplay)) + } + + @Test + fun testShow_presentationCreated() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + + manager.show() + + verify(presentationFactory).create(eq(secondaryDisplay)) + } + + @Test + @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND) + fun show_shadeMovesDisplay_newPresentationCreated() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + // Shade in the default display, we expect the presentation to be in the secondary only + shadePositionRepository.setDisplayId(defaultDisplay.displayId) + + manager.show() + + verify(presentationFactory).create(eq(secondaryDisplay)) + verify(presentationFactory, never()).create(eq(defaultDisplay)) + reset(presentationFactory) + whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation) + + // Let's move it to the secondary display. We expect it will be added in the default + // one. + shadePositionRepository.setDisplayId(secondaryDisplay.displayId) + testScope.advanceUntilIdle() + + verify(presentationFactory).create(eq(defaultDisplay)) + reset(presentationFactory) + whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation) + + // Let's move it back! it should be re-created (it means it was removed before) + shadePositionRepository.setDisplayId(defaultDisplay.displayId) + testScope.advanceUntilIdle() + + verify(presentationFactory).create(eq(secondaryDisplay)) + } + + @Test + @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND) + fun show_shadeInSecondaryDisplay_defaultOneHasPresentation() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + shadePositionRepository.setDisplayId(secondaryDisplay.displayId) + + manager.show() + + verify(presentationFactory).create(eq(defaultDisplay)) + } + + @Test + @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND) + fun show_shadeInDefaultDisplay_secondaryOneHasPresentation() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + shadePositionRepository.setDisplayId(defaultDisplay.displayId) + + manager.show() + + verify(presentationFactory).create(eq(secondaryDisplay)) + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 1342dd05d7f2..95830b5f4ed7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -15,6 +15,8 @@ */ package com.android.keyguard; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; + import android.annotation.NonNull; import android.app.Presentation; import android.content.Context; @@ -36,18 +38,24 @@ import android.view.WindowManager; import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.views.NavigationBarView; import com.android.systemui.settings.DisplayTracker; +import com.android.systemui.shade.data.repository.ShadePositionRepository; +import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.statusbar.policy.KeyguardStateController; import dagger.Lazy; +import kotlinx.coroutines.CoroutineScope; + import java.util.concurrent.Executor; import javax.inject.Inject; +import javax.inject.Provider; @SysUISingleton public class KeyguardDisplayManager { @@ -58,6 +66,7 @@ public class KeyguardDisplayManager { private final DisplayManager mDisplayService; private final DisplayTracker mDisplayTracker; private final Lazy mNavigationBarControllerLazy; + private final Provider mShadePositionRepositoryProvider; private final ConnectedDisplayKeyguardPresentation.Factory mConnectedDisplayKeyguardPresentationFactory; private final Context mContext; @@ -102,9 +111,12 @@ public class KeyguardDisplayManager { DeviceStateHelper deviceStateHelper, KeyguardStateController keyguardStateController, ConnectedDisplayKeyguardPresentation.Factory - connectedDisplayKeyguardPresentationFactory) { + connectedDisplayKeyguardPresentationFactory, + Provider shadePositionRepositoryProvider, + @Application CoroutineScope appScope) { mContext = context; mNavigationBarControllerLazy = navigationBarControllerLazy; + mShadePositionRepositoryProvider = shadePositionRepositoryProvider; uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class)); mDisplayService = mContext.getSystemService(DisplayManager.class); mDisplayTracker = displayTracker; @@ -112,6 +124,17 @@ public class KeyguardDisplayManager { mDeviceStateHelper = deviceStateHelper; mKeyguardStateController = keyguardStateController; mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory; + if (ShadeWindowGoesAround.isEnabled()) { + collectFlow(appScope, shadePositionRepositoryProvider.get().getDisplayId(), + (id) -> onShadeWindowMovedToDisplayId(id)); + } + } + + private void onShadeWindowMovedToDisplayId(int shadeDisplayId) { + if (mShowing) { + hidePresentation(shadeDisplayId); + updateDisplays(/* showing= */ true); + } } private boolean isKeyguardShowable(Display display) { @@ -119,9 +142,20 @@ public class KeyguardDisplayManager { if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display"); return false; } - if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) { - if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display"); - return false; + if (ShadeWindowGoesAround.isEnabled()) { + int shadeDisplayId = mShadePositionRepositoryProvider.get().getDisplayId().getValue(); + if (display.getDisplayId() == shadeDisplayId) { + if (DEBUG) { + Log.i(TAG, + "Do not show KeyguardPresentation on the shade window display"); + } + return false; + } + } else { + if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) { + if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display"); + return false; + } } display.getDisplayInfo(mTmpDisplayInfo); if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt index 63510b873951..e15830eb22eb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt @@ -157,7 +157,6 @@ object ShadeDisplayAwareModule { @SysUISingleton @Provides - @ShadeDisplayAware fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() return impl diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt new file mode 100644 index 000000000000..37210b90ee78 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadePositionRepository.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 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.shade.data.repository + +import android.view.Display +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeShadePositionRepository : ShadePositionRepository { + private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY) + + override fun setDisplayId(displayId: Int) { + _displayId.value = displayId + } + + override val displayId: StateFlow + get() = _displayId + + override fun resetDisplayId() { + _displayId.value = Display.DEFAULT_DISPLAY + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt index 6f492cfaa6c3..c23ff5302b3c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt @@ -32,7 +32,7 @@ object ShadeWindowGoesAround { /** Is the refactor enabled */ @JvmStatic - inline val isEnabled + inline val isEnabled: Boolean get() = Flags.shadeWindowGoesAround() /** -- cgit v1.2.3-59-g8ed1b