diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt index ee7f5ed8c86ea..39008eecd6a2b 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt @@ -15,14 +15,32 @@ */ package com.android.systemui.globalactions +import android.app.PendingIntent import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import android.net.Uri import android.service.quickaccesswallet.QuickAccessWalletClient +import android.util.Log +import android.view.LayoutInflater import android.view.ViewGroup +import android.widget.ImageView +import com.android.systemui.R import com.android.systemui.controls.controller.ControlsController import com.android.systemui.plugins.ActivityStarter import javax.inject.Inject -// Empty class, will be overridden for relevant devices +private const val TAG = "GlobalActionsInfo" + +/** Maximum number of times to show change info message */ +private const val MAX_VIEW_COUNT = 3 + +/** Maximum number of buttons allowed in landscape before this panel does not fit */ +private const val MAX_BUTTONS_LANDSCAPE = 4 + +private const val PREFERENCE = "global_actions_info_prefs" +private const val KEY_VIEW_COUNT = "view_count" + class GlobalActionsInfoProvider @Inject constructor( private val context: Context, private val walletClient: QuickAccessWalletClient, @@ -30,9 +48,69 @@ class GlobalActionsInfoProvider @Inject constructor( private val activityStarter: ActivityStarter ) { - fun addPanel(context: Context, parent: ViewGroup, nActions: Int, dismissParent: Runnable) { } + private var pendingIntent: PendingIntent + + init { + val url = context.resources.getString(R.string.global_actions_change_url) + val intent = Intent(Intent.ACTION_VIEW).apply { + setData(Uri.parse(url)) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + } + + fun addPanel(context: Context, parent: ViewGroup, nActions: Int, dismissParent: Runnable) { + // This panel does not fit on landscape with two rows of buttons showing, + // so skip adding the panel (and incrementing view counT) in that case + val isLandscape = + context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + if (isLandscape && nActions > MAX_BUTTONS_LANDSCAPE) { + return + } + + val view = LayoutInflater.from(context).inflate(R.layout.global_actions_change_panel, + parent, false) + val button = view.findViewById(R.id.global_actions_change_button) + button.setOnClickListener { _ -> + dismissParent.run() + activityStarter.postStartActivityDismissingKeyguard(pendingIntent) + } + parent.addView(view, 0) // Add to top + incrementViewCount() + } fun shouldShowMessage(): Boolean { - return false + // This is only relevant for some devices + val isEligible = context.resources.getBoolean( + R.bool.global_actions_show_change_info) + if (!isEligible) { + return false + } + + val sharedPrefs = context.getSharedPreferences(PREFERENCE, Context.MODE_PRIVATE) + + // Only show to users who previously had these items set up + val viewCount = if (sharedPrefs.contains(KEY_VIEW_COUNT) || hadContent()) { + sharedPrefs.getInt(KEY_VIEW_COUNT, 0) + } else { + -1 + } + + // Limit number of times this is displayed + return viewCount > -1 && viewCount < MAX_VIEW_COUNT + } + + private fun hadContent(): Boolean { + // Check whether user would have seen content in the power menu that has now moved + val hadControls = controlsController.getFavorites().size > 0 + val hadCards = walletClient.isWalletFeatureAvailable + Log.d(TAG, "Previously had controls $hadControls, cards $hadCards") + return hadControls || hadCards + } + + private fun incrementViewCount() { + val sharedPrefs = context.getSharedPreferences(PREFERENCE, Context.MODE_PRIVATE) + val count = sharedPrefs.getInt(KEY_VIEW_COUNT, 0) + sharedPrefs.edit().putInt(KEY_VIEW_COUNT, count + 1).apply() } } \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt index d3b9228fff6de..302a8d3f2efae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt @@ -14,45 +14,75 @@ * limitations under the License. */ -package com.android.systemui.globalactions +package com.google.android.systemui.globalactions +import android.content.Context +import android.content.SharedPreferences +import android.content.res.Configuration +import android.content.res.Resources import android.service.quickaccesswallet.QuickAccessWalletClient import android.testing.AndroidTestingRunner import android.view.ViewGroup import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase +import com.android.systemui.R import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.globalactions.GlobalActionsInfoProvider import com.android.systemui.plugins.ActivityStarter -import com.google.common.truth.Truth.assertThat +import com.android.systemui.SysuiTestCase +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyObject +import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.mock -import org.mockito.Mockito.verify import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +private const val PREFERENCE = "global_actions_info_prefs" +private const val KEY_VIEW_COUNT = "view_count" + +private fun eq(value: T): T = Mockito.eq(value) ?: value @SmallTest @RunWith(AndroidTestingRunner::class) class GlobalActionsInfoProviderTest : SysuiTestCase() { - @Mock - private lateinit var walletClient: QuickAccessWalletClient - @Mock - private lateinit var controlsController: ControlsController - @Mock - private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var walletClient: QuickAccessWalletClient + @Mock private lateinit var controlsController: ControlsController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var mockContext: Context + @Mock private lateinit var mockResources: Resources + @Mock private lateinit var sharedPrefs: SharedPreferences + @Mock private lateinit var sharedPrefsEditor: SharedPreferences.Editor private lateinit var infoProvider: GlobalActionsInfoProvider @Before fun setup() { MockitoAnnotations.initMocks(this) + mockContext = spy(context) + mockResources = spy(context.resources) + whenever(mockContext.resources).thenReturn(mockResources) + whenever(mockResources.getBoolean(R.bool.global_actions_show_change_info)) + .thenReturn(true) + whenever(mockContext.getSharedPreferences(eq(PREFERENCE), anyInt())) + .thenReturn(sharedPrefs) + whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor) + whenever(sharedPrefsEditor.putInt(anyString(), anyInt())).thenReturn(sharedPrefsEditor) + whenever(sharedPrefsEditor.putBoolean(anyString(), anyBoolean())) + .thenReturn(sharedPrefsEditor) + infoProvider = GlobalActionsInfoProvider( - context, + mockContext, walletClient, controlsController, activityStarter @@ -60,14 +90,45 @@ class GlobalActionsInfoProviderTest : SysuiTestCase() { } @Test - fun testAddPanel_doesNothing() { + fun testIsEligible_noCards() { + whenever(sharedPrefs.contains(eq(KEY_VIEW_COUNT))).thenReturn(false) + whenever(walletClient.isWalletFeatureAvailable).thenReturn(false) + + assertFalse(infoProvider.shouldShowMessage()) + } + + @Test + fun testIsEligible_hasCards() { + whenever(sharedPrefs.contains(eq(KEY_VIEW_COUNT))).thenReturn(false) + whenever(walletClient.isWalletFeatureAvailable).thenReturn(true) + + assertTrue(infoProvider.shouldShowMessage()) + } + + @Test + fun testNotEligible_shouldNotShow() { + whenever(mockResources.getBoolean(R.bool.global_actions_show_change_info)) + .thenReturn(false) + + assertFalse(infoProvider.shouldShowMessage()) + } + + @Test + fun testTooManyButtons_doesNotAdd() { + val configuration = Configuration() + configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + whenever(mockResources.configuration).thenReturn(configuration) + val parent = mock(ViewGroup::class.java) - infoProvider.addPanel(context, parent, 1, { }) + infoProvider.addPanel(mockContext, parent, 5, { }) + verify(parent, never()).addView(anyObject(), anyInt()) } @Test - fun testShouldShowPanel() { - assertThat(infoProvider.shouldShowMessage()).isFalse() + fun testLimitTimesShown() { + whenever(sharedPrefs.getInt(eq(KEY_VIEW_COUNT), anyInt())).thenReturn(4) + + assertFalse(infoProvider.shouldShowMessage()) } } \ No newline at end of file