diff options
8 files changed, 315 insertions, 15 deletions
diff --git a/packages/SystemUI/res/layout/controls_onboarding.xml b/packages/SystemUI/res/layout/controls_onboarding.xml new file mode 100644 index 000000000000..577a3b404472 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_onboarding.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:padding="4dp" + android:orientation="vertical"> + + <View + android:id="@+id/arrow" + android:elevation="2dp" + android:layout_width="10dp" + android:layout_height="8dp" + android:layout_marginBottom="-2dp" + android:layout_gravity="center_horizontal"/> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingStart="24dp" + android:paddingEnd="4dp" + android:background="@drawable/recents_onboarding_toast_rounded_background" + android:layout_gravity="center_horizontal" + android:elevation="2dp" + android:orientation="horizontal"> + + <TextView + android:id="@+id/onboarding_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="center_vertical" + android:textColor="?attr/wallpaperTextColor" + android:textSize="16sp"/> + <ImageView + android:id="@+id/dismiss" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_gravity="center_vertical" + android:padding="10dp" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:alpha="0.7" + android:src="@drawable/ic_close_white" + android:tint="?attr/wallpaperTextColor" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/accessibility_desc_close"/> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_structure_page.xml b/packages/SystemUI/res/layout/controls_structure_page.xml index 2c7e1681f2e1..047ab98eb191 100644 --- a/packages/SystemUI/res/layout/controls_structure_page.xml +++ b/packages/SystemUI/res/layout/controls_structure_page.xml @@ -15,17 +15,10 @@ ~ limitations under the License. --> -<androidx.core.widget.NestedScrollView +<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/listAll" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:layout_marginTop="@dimen/controls_management_list_margin"> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/listAll" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - -</androidx.core.widget.NestedScrollView>
\ No newline at end of file + android:layout_marginTop="@dimen/controls_management_list_margin"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index caf22fe16beb..18fec29dd3cf 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2653,4 +2653,7 @@ <string name="controls_pin_verify">Verify device PIN</string> <!-- Controls PIN entry dialog, text hint [CHAR LIMIT=30] --> <string name="controls_pin_instructions">Enter PIN</string> + + <!-- Tooltip to show in management screen when there are multiple structures [CHAR_LIMIT=50] --> + <string name="controls_structure_tooltip">Swipe to see other structures</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 125dd8f1d60c..47709108db1b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -650,6 +650,7 @@ <!-- Controls styles --> <style name="Theme.ControlsManagement" parent="@android:style/Theme.DeviceDefault.NoActionBar"> <item name="android:windowIsTranslucent">false</item> + <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item> </style> <style name="TextAppearance.Control"> diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 5e6589f76c13..6aa2326c388a 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -59,7 +59,8 @@ public final class Prefs { Key.TOUCHED_RINGER_TOGGLE, Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, Key.HAS_SEEN_BUBBLES_EDUCATION, - Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION + Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, + Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT }) public @interface Key { @Deprecated @@ -107,6 +108,7 @@ public final class Prefs { String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip"; String HAS_SEEN_BUBBLES_EDUCATION = "HasSeenBubblesOnboarding"; String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding"; + String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/TooltipManager.kt b/packages/SystemUI/src/com/android/systemui/controls/TooltipManager.kt new file mode 100644 index 000000000000..6e17bc94b16b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/TooltipManager.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2020 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.controls + +import android.annotation.StringRes +import android.content.Context +import android.graphics.CornerPathEffect +import android.graphics.drawable.ShapeDrawable +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.animation.AccelerateInterpolator +import android.view.animation.DecelerateInterpolator +import android.widget.TextView +import com.android.systemui.Prefs +import com.android.systemui.R +import com.android.systemui.recents.TriangleShape + +/** + * Manager for showing an onboarding tooltip on screen. + * + * The tooltip can be made to appear below or above a point. The number of times it will appear + * is determined by an shared preference (defined in [Prefs]). + * + * @property context A context to use to inflate the views and retrieve shared preferences from + * @property preferenceName name of the preference to use to track the number of times the tooltip + * has been shown. + * @property maxTimesShown the maximum number of times to show the tooltip + * @property below whether the tooltip should appear below (with up pointing arrow) or above (down + * pointing arrow) the specified point. + * @see [TooltipManager.show] + */ +class TooltipManager( + context: Context, + private val preferenceName: String, + private val maxTimesShown: Int = 2, + private val below: Boolean = true +) { + + companion object { + private const val SHOW_DELAY_MS: Long = 500 + private const val SHOW_DURATION_MS: Long = 300 + private const val HIDE_DURATION_MS: Long = 100 + } + + private var shown = Prefs.getInt(context, preferenceName, 0) + + val layout: ViewGroup = + LayoutInflater.from(context).inflate(R.layout.controls_onboarding, null) as ViewGroup + val preferenceStorer = { num: Int -> + Prefs.putInt(context, preferenceName, num) + } + + init { + layout.alpha = 0f + } + + private val textView = layout.requireViewById<TextView>(R.id.onboarding_text) + private val dismissView = layout.requireViewById<View>(R.id.dismiss).apply { + setOnClickListener { + hide(true) + } + } + + private val arrowView = layout.requireViewById<View>(R.id.arrow).apply { + val typedValue = TypedValue() + context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true) + val toastColor = context.resources.getColor(typedValue.resourceId, context.theme) + val arrowRadius = context.resources.getDimensionPixelSize( + R.dimen.recents_onboarding_toast_arrow_corner_radius) + val arrowLp = layoutParams + val arrowDrawable = ShapeDrawable(TriangleShape.create( + arrowLp.width.toFloat(), arrowLp.height.toFloat(), below)) + val arrowPaint = arrowDrawable.paint + arrowPaint.color = toastColor + // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. + arrowPaint.pathEffect = CornerPathEffect(arrowRadius.toFloat()) + setBackground(arrowDrawable) + } + + init { + if (!below) { + layout.removeView(arrowView) + layout.addView(arrowView) + (arrowView.layoutParams as ViewGroup.MarginLayoutParams).apply { + bottomMargin = topMargin + topMargin = 0 + } + } + } + + /** + * Show the tooltip + * + * @param stringRes the id of the string to show in the tooltip + * @param x horizontal position (w.r.t. screen) for the arrow point + * @param y vertical position (w.r.t. screen) for the arrow point + */ + fun show(@StringRes stringRes: Int, x: Int, y: Int) { + if (!shouldShow()) return + textView.setText(stringRes) + shown++ + preferenceStorer(shown) + layout.post { + val p = IntArray(2) + layout.getLocationOnScreen(p) + layout.translationX = (x - p[0] - layout.width / 2).toFloat() + layout.translationY = (y - p[1]).toFloat() - if (!below) layout.height else 0 + if (layout.alpha == 0f) { + layout.animate() + .alpha(1f) + .withLayer() + .setStartDelay(SHOW_DELAY_MS) + .setDuration(SHOW_DURATION_MS) + .setInterpolator(DecelerateInterpolator()) + .start() + } + } + } + + /** + * Hide the tooltip + * + * @param animate whether to animate the fade out + */ + fun hide(animate: Boolean = false) { + if (layout.alpha == 0f) return + layout.post { + if (animate) { + layout.animate() + .alpha(0f) + .withLayer() + .setStartDelay(0) + .setDuration(HIDE_DURATION_MS) + .setInterpolator(AccelerateInterpolator()) + .start() + } else { + layout.animate().cancel() + layout.alpha = 0f + } + } + } + + private fun shouldShow() = shown < maxTimesShown +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 04715abe5f99..502354a8703a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -19,22 +19,27 @@ package com.android.systemui.controls.management import android.app.Activity import android.content.ComponentName import android.content.Intent +import android.content.res.Configuration import android.graphics.drawable.Drawable import android.os.Bundle import android.text.TextUtils +import android.view.Gravity import android.view.View +import android.view.ViewGroup import android.view.ViewStub import android.widget.Button +import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView import androidx.viewpager2.widget.ViewPager2 +import com.android.systemui.Prefs import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlsServiceInfo +import com.android.systemui.controls.TooltipManager import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.qs.PageIndicator import com.android.systemui.settings.CurrentUserTracker import java.text.Collator import java.util.concurrent.Executor @@ -51,6 +56,8 @@ class ControlsFavoritingActivity @Inject constructor( companion object { private const val TAG = "ControlsFavoritingActivity" const val EXTRA_APP = "extra_app_label" + private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT + private const val TOOLTIP_MAX_SHOWN = 2 } private var component: ComponentName? = null @@ -61,7 +68,8 @@ class ControlsFavoritingActivity @Inject constructor( private lateinit var titleView: TextView private lateinit var iconView: ImageView private lateinit var iconFrame: View - private lateinit var pageIndicator: PageIndicator + private lateinit var pageIndicator: ManagementPageIndicator + private var mTooltipManager: TooltipManager? = null private var listOfStructures = emptyList<StructureContainer>() private lateinit var comparator: Comparator<StructureContainer> @@ -172,9 +180,48 @@ class ControlsFavoritingActivity @Inject constructor( layoutResource = R.layout.controls_management_favorites inflate() } - statusText = requireViewById(R.id.status_message) - pageIndicator = requireViewById(R.id.structure_page_indicator) + if (shouldShowTooltip()) { + mTooltipManager = TooltipManager(statusText.context, + TOOLTIP_PREFS_KEY, TOOLTIP_MAX_SHOWN) + addContentView( + mTooltipManager?.layout, + FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + Gravity.TOP or Gravity.LEFT + ) + ) + } + pageIndicator = requireViewById<ManagementPageIndicator>( + R.id.structure_page_indicator).apply { + addOnLayoutChangeListener(object : View.OnLayoutChangeListener { + override fun onLayoutChange( + v: View, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + if (v.visibility == View.VISIBLE && mTooltipManager != null) { + val p = IntArray(2) + v.getLocationOnScreen(p) + val x = p[0] + (right - left) / 2 + val y = p[1] + bottom - top + mTooltipManager?.show(R.string.controls_structure_tooltip, x, y) + } + } + }) + visibilityListener = { + if (it != View.VISIBLE) { + mTooltipManager?.hide(true) + } + } + } titleView = requireViewById<TextView>(R.id.title).apply { text = appName ?: resources.getText(R.string.controls_favorite_default_title) @@ -184,6 +231,12 @@ class ControlsFavoritingActivity @Inject constructor( iconView = requireViewById(com.android.internal.R.id.icon) iconFrame = requireViewById(R.id.icon_frame) structurePager = requireViewById<ViewPager2>(R.id.structure_pager) + structurePager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + mTooltipManager?.hide(true) + } + }) bindButtons() } @@ -207,11 +260,25 @@ class ControlsFavoritingActivity @Inject constructor( } } + override fun onPause() { + super.onPause() + mTooltipManager?.hide(false) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + mTooltipManager?.hide(false) + } + override fun onDestroy() { currentUserTracker.stopTracking() listingController.removeCallback(listingCallback) super.onDestroy() } + + private fun shouldShowTooltip(): Boolean { + return Prefs.getInt(applicationContext, TOOLTIP_PREFS_KEY, 0) < TOOLTIP_MAX_SHOWN + } } data class StructureContainer(val structureName: CharSequence, val model: ControlsModel) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt index 4289274cb3e4..72b10982e237 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt @@ -40,4 +40,13 @@ class ManagementPageIndicator( super.setLocation(location) } } + + var visibilityListener: (Int) -> Unit = {} + + override fun onVisibilityChanged(changedView: View, visibility: Int) { + super.onVisibilityChanged(changedView, visibility) + if (changedView == this) { + visibilityListener(visibility) + } + } }
\ No newline at end of file |