Implementing available part of UX spec for DW toast
See https://docs.google.com/presentation/d/1AepsnLeKcRhjMW35SkB5yMKO3u6waigug8Tyfe0LO5o/edit#slide=id.g4c5ab81849_0_0 Also using time formatting code copy-pasted from Google DWB app. Bug: 118319143 Tests: Manual Change-Id: I84392d7655f402e38cf4c46ae530d06f755a7df8
This commit is contained in:
parent
ff9571b30f
commit
759db43f3b
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="#E61A73E8" />
|
||||
<corners android:radius="@dimen/task_corner_radius" />
|
||||
</shape>
|
|
@ -38,9 +38,11 @@
|
|||
<com.android.quickstep.views.DigitalWellBeingToast
|
||||
android:id="@+id/digital_well_being_toast"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_height="48dp"
|
||||
android:importantForAccessibility="noHideDescendants"
|
||||
android:background="#800000FF"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/bg_wellbeing_toast"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/white"
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<!-- Application name -->
|
||||
<string name="derived_app_name" translatable="false">Quickstep</string>
|
||||
|
@ -46,4 +46,24 @@
|
|||
|
||||
<!-- Accessibility title for the list of recent apps [CHAR_LIMIT=none] -->
|
||||
<string name="accessibility_recent_apps">Recent apps</string>
|
||||
|
||||
<!-- Accessibility title for an app card in Recents for apps that have time limit set
|
||||
[CHAR_LIMIT=none] -->
|
||||
<string name="task_contents_description_with_remaining_time"><xliff:g id="task_description" example="GMail">%1$s</xliff:g>, <xliff:g id="remaining_time" example="7 minutes left today">%2$s</xliff:g></string>
|
||||
|
||||
<!-- Text to show total app usage per day if it is less than 1 minute ("<" is the
|
||||
escaped form of '<'). [CHAR LIMIT=10] -->
|
||||
<string name="shorter_duration_less_than_one_minute">< 1 minute</string>
|
||||
|
||||
<!-- Annotation shown on an app card in Recents, telling that the app was switched to a
|
||||
grayscale because it ran over its time limit [CHAR LIMIT=25] -->
|
||||
<string name="app_in_grayscale">App in grayscale</string>
|
||||
|
||||
<!-- Annotation shown on an app card in Recents, telling that the app has a usage limit set by
|
||||
the user, and a given time is left for it today [CHAR LIMIT=20] -->
|
||||
<string name="time_left_for_app"><xliff:g id="time" example="7 minutes">%1$s</xliff:g> left today</string>
|
||||
|
||||
<!-- Annotation shown on an app card in Recents, telling that the app is in a group that has a
|
||||
usage limit set by the user, and a given time is left for the group today [CHAR LIMIT=20] -->
|
||||
<string name="time_left_for_group"><xliff:g id="time" example="1 hour">%1$s</xliff:g> left for group</string>
|
||||
</resources>
|
|
@ -20,17 +20,27 @@ import android.app.ActivityOptions;
|
|||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.icu.text.MeasureFormat;
|
||||
import android.icu.text.MeasureFormat.FormatWidth;
|
||||
import android.icu.util.Measure;
|
||||
import android.icu.util.MeasureUnit;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class DigitalWellBeingToast extends TextView {
|
||||
|
||||
public interface InitializeCallback {
|
||||
|
@ -65,16 +75,78 @@ public final class DigitalWellBeingToast extends TextView {
|
|||
|
||||
callback.call(
|
||||
appUsageLimitTimeMs >= 0 && appRemainingTimeMs < 0 ? 0 : 1,
|
||||
getContentDescriptionForTask(task, appRemainingTimeMs, isGroupLimit));
|
||||
getContentDescriptionForTask(
|
||||
task, appUsageLimitTimeMs, appRemainingTimeMs, isGroupLimit));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static String getText(long remainingTime, boolean isGroupLimit) {
|
||||
return remainingTime < 0 ?
|
||||
"Grayed" :
|
||||
"Remaining time:" + (remainingTime + 59999) / 60000
|
||||
+ " min " + (isGroupLimit ? "for group" : "for the app");
|
||||
private String getReadableDuration(
|
||||
Duration duration,
|
||||
FormatWidth formatWidthHourAndMinute,
|
||||
@StringRes int durationLessThanOneMinuteStringId,
|
||||
boolean forceFormatWidth) {
|
||||
int hours = Math.toIntExact(duration.toHours());
|
||||
int minutes = Math.toIntExact(duration.minusHours(hours).toMinutes());
|
||||
|
||||
// Apply formatWidthHourAndMinute if both the hour part and the minute part are non-zero.
|
||||
if (hours > 0 && minutes > 0) {
|
||||
return MeasureFormat.getInstance(Locale.getDefault(), formatWidthHourAndMinute)
|
||||
.formatMeasures(
|
||||
new Measure(hours, MeasureUnit.HOUR),
|
||||
new Measure(minutes, MeasureUnit.MINUTE));
|
||||
}
|
||||
|
||||
// Apply formatWidthHourOrMinute if only the hour part is non-zero (unless forced).
|
||||
if (hours > 0) {
|
||||
return MeasureFormat.getInstance(
|
||||
Locale.getDefault(),
|
||||
forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
|
||||
.formatMeasures(new Measure(hours, MeasureUnit.HOUR));
|
||||
}
|
||||
|
||||
// Apply formatWidthHourOrMinute if only the minute part is non-zero (unless forced).
|
||||
if (minutes > 0) {
|
||||
return MeasureFormat.getInstance(
|
||||
Locale.getDefault()
|
||||
, forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
|
||||
.formatMeasures(new Measure(minutes, MeasureUnit.MINUTE));
|
||||
}
|
||||
|
||||
// Use a specific string for usage less than one minute but non-zero.
|
||||
if (duration.compareTo(Duration.ZERO) > 0) {
|
||||
return getResources().getString(durationLessThanOneMinuteStringId);
|
||||
}
|
||||
|
||||
// Otherwise, return 0-minute string.
|
||||
return MeasureFormat.getInstance(
|
||||
Locale.getDefault(), forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
|
||||
.formatMeasures(new Measure(0, MeasureUnit.MINUTE));
|
||||
}
|
||||
|
||||
private String getReadableDuration(
|
||||
Duration duration,
|
||||
FormatWidth formatWidthHourAndMinute,
|
||||
@StringRes int durationLessThanOneMinuteStringId) {
|
||||
return getReadableDuration(
|
||||
duration,
|
||||
formatWidthHourAndMinute,
|
||||
durationLessThanOneMinuteStringId,
|
||||
/* forceFormatWidth= */ false);
|
||||
}
|
||||
|
||||
private String getShorterReadableDuration(Duration duration) {
|
||||
return getReadableDuration(
|
||||
duration, FormatWidth.NARROW, R.string.shorter_duration_less_than_one_minute);
|
||||
}
|
||||
|
||||
private String getText(long remainingTime, boolean isGroupLimit) {
|
||||
final Resources resources = getResources();
|
||||
return (remainingTime < 0) ?
|
||||
resources.getString(R.string.app_in_grayscale) :
|
||||
resources.getString(
|
||||
isGroupLimit ? R.string.time_left_for_group : R.string.time_left_for_app,
|
||||
getShorterReadableDuration(Duration.ofMillis(remainingTime)));
|
||||
}
|
||||
|
||||
public void openAppUsageSettings() {
|
||||
|
@ -97,8 +169,8 @@ public final class DigitalWellBeingToast extends TextView {
|
|||
}
|
||||
|
||||
private String getContentDescriptionForTask(
|
||||
Task task, long appRemainingTimeMs, boolean isGroupLimit) {
|
||||
return appRemainingTimeMs > 0 ?
|
||||
Task task, long appUsageLimitTimeMs, long appRemainingTimeMs, boolean isGroupLimit) {
|
||||
return appUsageLimitTimeMs > 0 ?
|
||||
getResources().getString(
|
||||
R.string.task_contents_description_with_remaining_time,
|
||||
task.titleDescription,
|
||||
|
|
|
@ -336,6 +336,4 @@
|
|||
|
||||
<!-- Failed action error message: e.g. Failed: Pause -->
|
||||
<string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
|
||||
|
||||
<string name="task_contents_description_with_remaining_time" translatable="false"><xliff:g id="task_description" example="GMail">%1$s</xliff:g>, <xliff:g id="remaining_time" example="7 minutes">%2$s</xliff:g></string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue