Add layout for 1x1 size, update 2x1

This allows users a smaller width resize for the People Tile widget, and
adjust the view accordingly after resize. Implements 1x1 and 2x1 mocks,
with polish remaining for 2x1.
Also fixes a crash when launching the conversation picker and bug on
removing missed call notifications.

Bug: 182377480
Test: PeopleSpaceUtilsTest
Change-Id: Ie3b16fb3e348f8edd7bb314c9f43408dca7b43e2
This commit is contained in:
Anna Zappone
2021-03-04 21:22:13 +00:00
parent 11a7f13364
commit c531f63f8c
14 changed files with 697 additions and 422 deletions

View File

@@ -0,0 +1,24 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="48"
android:viewportHeight="48"
android:width="48dp"
android:height="48dp">
<path
android:pathData="M40 4H8C5.79 4 4.02 5.79 4.02 8L4 44l8 -8h28c2.21 0 4 -1.79 4 -4V8c0 -2.21 -1.79 -4 -4 -4zM12 18h24v4H12v-4zm16 10H12v-4h16v4zm8 -12H12v-4h24v4z"
android:fillColor="#FF000000" />
</vector>

View File

@@ -0,0 +1,28 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
</vector>

View File

@@ -28,48 +28,27 @@
<LinearLayout
android:orientation="horizontal"
android:gravity="center"
android:layout_gravity="center"
android:paddingVertical="2dp"
android:paddingHorizontal="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:background="@drawable/people_space_new_story_outline"
android:id="@+id/person_icon_with_story"
android:gravity="center_horizontal"
android:layout_width="60dp"
android:layout_height="60dp">
<ImageView
android:id="@+id/person_icon_inside_ring"
android:layout_marginEnd="4dp"
android:layout_marginStart="4dp"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
android:layout_width="52dp"
android:layout_height="52dp"/>
</LinearLayout>
<ImageView
android:id="@+id/person_icon_only"
android:id="@+id/person_icon"
android:layout_width="60dp"
android:layout_height="60dp"/>
<ImageView
android:id="@+id/package_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="-20dp"
android:layout_marginTop="22dp"/>
android:id="@+id/availability"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/circle_green_10dp"/>
<LinearLayout
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingStart="4dp"
android:gravity="top"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/availability"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/circle_green_10dp"/>
<TextView
android:id="@+id/name"
android:text="@string/empty_user_name"

View File

@@ -1,154 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:background="@drawable/people_space_tile_view_card"
android:id="@+id/item"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/punctuation_layout"/>
<RelativeLayout
android:gravity="start"
android:id="@+id/column_one"
android:paddingVertical="10dp"
android:paddingStart="8dp"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="@+id/subtext"
android:layout_toStartOf="@+id/content_layout"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:gravity="top|start"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textSize="12sp"
android:maxWidth="60dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"/>
<LinearLayout
android:orientation="horizontal"
android:id="@+id/avatar_and_app_icon"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/person_icon_with_story"
android:background="@drawable/people_space_new_story_outline"
android:gravity="center_horizontal"
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:id="@+id/person_icon_inside_ring"
android:layout_marginEnd="4dp"
android:layout_marginStart="4dp"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
android:layout_width="40dp"
android:layout_height="40dp"/>
</LinearLayout>
<ImageView
android:id="@+id/person_icon_only"
android:layout_width="48dp"
android:layout_height="48dp"/>
<ImageView
android:id="@id/package_icon"
android:layout_marginStart="-16dp"
android:layout_marginTop="18dp"
android:paddingBottom="10dp"
android:paddingEnd="8dp"
android:layout_width="28dp"
android:layout_height="32dp"/>
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:id="@+id/content_layout"
android:paddingBottom="4dp"
android:paddingStart="4dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_toEndOf="@id/column_one"
android:gravity="top|end"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/content"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:gravity="top|end"
android:textSize="12sp"
android:paddingTop="2dp"
android:paddingEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end"/>
<LinearLayout
android:id="@+id/content_background"
android:background="@drawable/people_space_content_background"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image"
android:adjustViewBounds="true"
android:maxHeight="44dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/person_label"
android:paddingBottom="10dp"
android:paddingEnd="8dp"
android:gravity="start|bottom"
android:layout_toEndOf="@id/column_one"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_below="@id/content_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/availability"
android:layout_width="10dp"
android:layout_height="10dp"
android:paddingVertical="2dp"
android:background="@drawable/circle_green_10dp"/>
<TextView
android:id="@+id/name"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View File

@@ -1,44 +1,69 @@
<?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.
-->
~ 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_gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
<LinearLayout
android:background="@drawable/people_space_tile_view_card"
android:id="@+id/item"
android:orientation="vertical"
android:layout_gravity="center"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:gravity="start"
android:id="@+id/column_one"
android:paddingVertical="8dp"
android:paddingStart="8dp"
android:layout_width="wrap_content"
android:layout_height="match_parent">
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:gravity="top"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:gravity="start"
android:id="@+id/person_icon"
android:layout_width="56dp"
android:layout_height="56dp"/>
<ImageView
android:id="@+id/availability"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/circle_green_10dp"/>
<LinearLayout
android:orientation="vertical"
android:gravity="top|start"
android:paddingStart="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/subtext"
android:text="@string/empty_user_name"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
android:paddingBottom="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"/>
<LinearLayout
android:orientation="horizontal"
android:id="@+id/content_background"
android:background="@drawable/people_space_content_background"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_width="60dp"
android:layout_height="60dp">
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image"
android:gravity="center"
@@ -46,105 +71,33 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"/>
<ImageView
android:id="@+id/status_defined_icon"
android:gravity="start|top"
android:layout_marginStart="-52dp"
android:layout_marginTop="8dp"
android:layout_width="18dp"
android:layout_height="18dp"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:id="@+id/avatar_and_app_icon"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:paddingStart="4dp"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/person_icon_with_story"
android:background="@drawable/people_space_new_story_outline"
android:gravity="center_horizontal"
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:id="@+id/person_icon_inside_ring"
android:layout_marginEnd="4dp"
android:layout_marginStart="4dp"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
android:layout_width="40dp"
android:layout_height="40dp"/>
</LinearLayout>
<ImageView
android:id="@+id/person_icon_only"
android:layout_width="48dp"
android:layout_height="48dp"/>
<ImageView
android:id="@id/package_icon"
android:layout_marginStart="-16dp"
android:layout_marginTop="18dp"
android:paddingBottom="10dp"
android:paddingEnd="8dp"
android:layout_width="28dp"
android:layout_height="32dp"/>
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:id="@+id/content_layout"
android:paddingTop="10dp"
android:paddingBottom="4dp"
android:paddingHorizontal="8dp"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_toEndOf="@id/column_one"
android:gravity="top|end"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/status"
android:id="@+id/text_content"
android:text="@string/empty_status"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
android:singleLine="false"
android:ellipsize="end"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/person_label"
android:paddingBottom="10dp"
android:paddingEnd="8dp"
android:gravity="start|bottom"
android:layout_toEndOf="@id/column_one"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_below="@id/content_layout"
android:orientation="vertical"
android:gravity="bottom"
android:orientation="horizontal"
android:paddingTop="4dp"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/time"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textSize="12sp"
android:paddingVertical="2dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:visibility="gone"
android:ellipsize="end"/>
<ImageView
android:id="@+id/availability"
android:layout_width="10dp"
android:layout_height="10dp"
android:paddingVertical="2dp"
android:background="@drawable/circle_green_10dp"/>
<TextView
android:id="@+id/name"
android:gravity="center_vertical"
android:layout_weight="1"
android:text="@string/empty_user_name"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp"
@@ -152,6 +105,13 @@
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/predefined_icon"
android:gravity="end"
android:paddingStart="6dp"
android:layout_width="24dp"
android:layout_height="18dp"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/people_space_tile_view_card"
android:orientation="vertical"
android:paddingHorizontal="4dp"
android:paddingVertical="8dp">
<ImageView
android:id="@+id/person_icon"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<ImageView
android:id="@+id/predefined_icon"
android:layout_gravity="center"
android:paddingTop="4dp"
android:layout_width="18dp"
android:layout_height="22dp"
android:layout_weight="1" />
<TextView
android:id="@+id/name"
android:layout_gravity="center"
android:paddingTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:paddingHorizontal="8dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp" />
</LinearLayout>
</FrameLayout>

View File

@@ -15,10 +15,11 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="140dp"
android:minHeight="55dp"
android:minResizeWidth="110dp"
android:minResizeHeight="55dp"
android:minWidth="120dp"
android:minHeight="54dp"
android:minResizeWidth="60dp"
android:minResizeHeight="54dp"
android:maxResizeHeight="207dp"
android:updatePeriodMillis="60000"
android:description="@string/people_tile_description"
android:previewLayout="@layout/people_space_placeholder_layout"

View File

@@ -110,16 +110,16 @@ public class PeopleProvider extends ContentProvider {
}
if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
RemoteViews view = PeopleSpaceUtils.createRemoteViews(getContext(), tile, 0);
final Bundle bundle = new Bundle();
RemoteViews view = PeopleSpaceUtils.createRemoteViews(getContext(), tile, 0, bundle);
bundle.putParcelable(PeopleProviderUtils.RESPONSE_KEY_REMOTE_VIEWS, view);
return bundle;
}
private boolean doesCallerHavePermission() {
return getContext().checkPermission(
PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
Binder.getCallingPid(), Binder.getCallingUid())
PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
Binder.getCallingPid(), Binder.getCallingUid())
== PackageManager.PERMISSION_GRANTED;
}

View File

@@ -26,6 +26,10 @@ import static android.app.people.ConversationStatus.ACTIVITY_MEDIA;
import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
import android.annotation.Nullable;
import android.app.INotificationManager;
@@ -41,6 +45,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.SQLException;
import android.graphics.Bitmap;
@@ -52,13 +57,16 @@ import android.icu.text.MeasureFormat;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.RemoteViews;
@@ -110,11 +118,17 @@ public class PeopleSpaceUtils {
public static final String SHORTCUT_ID = "shortcut_id";
public static final String EMPTY_STRING = "";
public static final int INVALID_WIDGET_ID = -1;
public static final int INVALID_USER_ID = -1;
public static final PeopleTileKey EMPTY_KEY =
new PeopleTileKey(EMPTY_STRING, INVALID_USER_ID, EMPTY_STRING);
public static final int SMALL_LAYOUT = 0;
public static final int MEDIUM_LAYOUT = 1;
@VisibleForTesting
static final int REQUIRED_WIDTH_FOR_MEDIUM = 146;
private static final int AVATAR_SIZE_FOR_MEDIUM = 56;
private static final int DEFAULT_WIDTH = 146;
private static final int DEFAULT_HEIGHT = 92;
private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
@@ -208,7 +222,8 @@ public class PeopleSpaceUtils {
//TODO: Delete app widget id when crash is fixed (b/172932636)
continue;
}
updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, tile);
Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
updateAppWidgetViews(appWidgetManager, context, appWidgetId, tile, options);
widgetIdToTile.put(appWidgetId, tile);
}
getBirthdaysOnBackgroundThread(context, appWidgetManager, widgetIdToTile, appWidgetIds);
@@ -343,7 +358,10 @@ public class PeopleSpaceUtils {
.stream()
.filter(entry -> entry.getRanking() != null
&& entry.getRanking().getConversationShortcutInfo() != null)
.collect(Collectors.toMap(PeopleTileKey::new, e -> e));
.collect(Collectors.toMap(PeopleTileKey::new, e -> e,
// Handle duplicate keys to avoid crashes.
(e1, e2) -> e1.getSbn().getNotification().when
> e2.getSbn().getNotification().when ? e1 : e2));
if (DEBUG) {
Log.d(TAG, "Number of visible notifications:" + visibleNotifications.size());
}
@@ -401,9 +419,11 @@ public class PeopleSpaceUtils {
/** Creates a {@link RemoteViews} for {@code tile}. */
public static RemoteViews createRemoteViews(Context context,
PeopleSpaceTile tile, int appWidgetId) {
RemoteViews viewsForTile = getViewForTile(context, tile);
RemoteViews views = setCommonRemoteViewsFields(context, viewsForTile, tile);
PeopleSpaceTile tile, int appWidgetId, Bundle options) {
int layoutSize = getLayoutSize(context, options);
RemoteViews viewsForTile = getViewForTile(context, tile, layoutSize);
int maxAvatarSize = getMaxAvatarSize(context, options, layoutSize);
RemoteViews views = setCommonRemoteViewsFields(context, viewsForTile, tile, maxAvatarSize);
return setLaunchIntents(context, views, tile, appWidgetId);
}
@@ -411,15 +431,16 @@ public class PeopleSpaceUtils {
* The prioritization for the {@code tile} content is missed calls, followed by notification
* content, then birthdays, then the most recent status, and finally last interaction.
*/
private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile) {
private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile,
int layoutSize) {
if (Objects.equals(tile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
if (DEBUG) Log.d(TAG, "Create missed call view");
return createMissedCallRemoteViews(context, tile);
return createMissedCallRemoteViews(context, tile, layoutSize);
}
if (tile.getNotificationKey() != null) {
if (DEBUG) Log.d(TAG, "Create notification view");
return createNotificationRemoteViews(context, tile);
return createNotificationRemoteViews(context, tile, layoutSize);
}
// TODO: Add sorting when we expose timestamp of statuses.
@@ -429,7 +450,7 @@ public class PeopleSpaceUtils {
ConversationStatus birthdayStatus = getBirthdayStatus(tile, statusesForEntireView);
if (birthdayStatus != null) {
if (DEBUG) Log.d(TAG, "Create birthday view");
return createStatusRemoteViews(context, birthdayStatus);
return createStatusRemoteViews(context, birthdayStatus, layoutSize);
}
if (!statusesForEntireView.isEmpty()) {
@@ -437,10 +458,48 @@ public class PeopleSpaceUtils {
Log.d(TAG,
"Create status view for: " + statusesForEntireView.get(0).getActivity());
}
return createStatusRemoteViews(context, statusesForEntireView.get(0));
return createStatusRemoteViews(context, statusesForEntireView.get(0), layoutSize);
}
return createLastInteractionRemoteViews(context, tile);
return createLastInteractionRemoteViews(context, tile, layoutSize);
}
/** Calculates the best layout relative to the size in {@code options}. */
private static int getLayoutSize(Context context, Bundle options) {
int display = context.getResources().getConfiguration().orientation;
int width = display == Configuration.ORIENTATION_PORTRAIT
? options.getInt(OPTION_APPWIDGET_MIN_WIDTH, DEFAULT_WIDTH) : options.getInt(
OPTION_APPWIDGET_MAX_WIDTH, DEFAULT_WIDTH);
int height = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
OPTION_APPWIDGET_MAX_HEIGHT, DEFAULT_HEIGHT)
: options.getInt(OPTION_APPWIDGET_MIN_HEIGHT, DEFAULT_HEIGHT);
// Small layout used below a certain minimum width with any height.
if (width < REQUIRED_WIDTH_FOR_MEDIUM) {
if (DEBUG) Log.d(TAG, "Small view for width: " + width + " height: " + height);
return SMALL_LAYOUT;
}
if (DEBUG) Log.d(TAG, "Medium view for width: " + width + " height: " + height);
return MEDIUM_LAYOUT;
}
/** Returns the max avatar size for {@code layoutSize} under the current {@code options}. */
private static int getMaxAvatarSize(Context context, Bundle options, int layoutSize) {
int avatarHeightSpace = AVATAR_SIZE_FOR_MEDIUM;
int avatarWidthSpace = AVATAR_SIZE_FOR_MEDIUM;
if (layoutSize == SMALL_LAYOUT) {
int display = context.getResources().getConfiguration().orientation;
int width = display == Configuration.ORIENTATION_PORTRAIT
? options.getInt(OPTION_APPWIDGET_MIN_WIDTH, DEFAULT_WIDTH) : options.getInt(
OPTION_APPWIDGET_MAX_WIDTH, DEFAULT_WIDTH);
int height = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
OPTION_APPWIDGET_MAX_HEIGHT, DEFAULT_HEIGHT)
: options.getInt(OPTION_APPWIDGET_MIN_HEIGHT, DEFAULT_HEIGHT);
avatarHeightSpace = height - (8 + 4 + 18 + 8);
avatarWidthSpace = width - (4 + 4);
}
if (DEBUG) Log.d(TAG, "Height: " + avatarHeightSpace + " width: " + avatarWidthSpace);
return Math.min(avatarHeightSpace, avatarWidthSpace);
}
@Nullable
@@ -478,14 +537,22 @@ public class PeopleSpaceUtils {
}
}
private static RemoteViews createStatusRemoteViews(Context context, ConversationStatus status) {
RemoteViews views = new RemoteViews(
context.getPackageName(), R.layout.people_space_small_avatar_tile);
private static RemoteViews createStatusRemoteViews(Context context, ConversationStatus status,
int layoutSize) {
int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
: R.layout.people_space_small_avatar_tile;
RemoteViews views = new RemoteViews(context.getPackageName(), layout);
CharSequence statusText = status.getDescription();
if (TextUtils.isEmpty(statusText)) {
statusText = getStatusTextByType(context, status.getActivity());
}
views.setTextViewText(R.id.status, statusText);
views.setViewVisibility(R.id.subtext, View.GONE);
views.setViewVisibility(R.id.text_content, View.VISIBLE);
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true);
int secondaryTextColor = context.getColor(typedValue.resourceId);
views.setInt(R.id.text_content, "setTextColor", secondaryTextColor);
views.setTextViewText(R.id.text_content, statusText);
Icon statusIcon = status.getIcon();
if (statusIcon != null) {
views.setImageViewIcon(R.id.image, statusIcon);
@@ -494,6 +561,8 @@ public class PeopleSpaceUtils {
views.setViewVisibility(R.id.content_background, View.GONE);
}
// TODO: Set status pre-defined icons
views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_person);
ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
return views;
}
@@ -519,7 +588,7 @@ public class PeopleSpaceUtils {
}
private static RemoteViews setCommonRemoteViewsFields(Context context, RemoteViews views,
PeopleSpaceTile tile) {
PeopleSpaceTile tile, int maxAvatarSize) {
try {
boolean isAvailable =
tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
@@ -532,27 +601,20 @@ public class PeopleSpaceUtils {
boolean hasNewStory =
tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
c -> c.getActivity() == ACTIVITY_NEW_STORY);
if (hasNewStory) {
views.setViewVisibility(R.id.person_icon_with_story, View.VISIBLE);
views.setViewVisibility(R.id.person_icon_only, View.GONE);
views.setImageViewIcon(R.id.person_icon_inside_ring, tile.getUserIcon());
} else {
views.setViewVisibility(R.id.person_icon_with_story, View.GONE);
views.setViewVisibility(R.id.person_icon_only, View.VISIBLE);
views.setImageViewIcon(R.id.person_icon_only, tile.getUserIcon());
}
views.setTextViewText(R.id.name, tile.getUserName().toString());
views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
views.setBoolean(R.id.content_background, "setClipToOutline", true);
Icon icon = tile.getUserIcon();
PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
context.getPackageManager(),
IconDrawableFactory.newInstance(context, false),
maxAvatarSize);
Drawable drawable = icon.loadDrawable(context);
Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable,
tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
hasNewStory);
Bitmap bitmap = convertDrawableToBitmap(personDrawable);
views.setImageViewBitmap(R.id.person_icon, bitmap);
views.setImageViewBitmap(
R.id.package_icon,
PeopleSpaceUtils.convertDrawableToBitmap(
context.getPackageManager().getApplicationIcon(
tile.getPackageName())
)
);
return views;
} catch (Exception e) {
Log.e(TAG, "Failed to set common fields: " + e);
@@ -585,47 +647,77 @@ public class PeopleSpaceUtils {
} catch (Exception e) {
Log.e(TAG, "Failed to add launch intents: " + e);
}
return views;
}
private static RemoteViews createMissedCallRemoteViews(Context context,
PeopleSpaceTile tile) {
RemoteViews views = new RemoteViews(
context.getPackageName(), R.layout.people_space_small_avatar_tile);
views.setTextViewText(R.id.status, tile.getNotificationContent());
views.setImageViewResource(R.id.status_defined_icon, R.drawable.ic_phone_missed);
PeopleSpaceTile tile, int layoutSize) {
int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
: R.layout.people_space_small_avatar_tile;
RemoteViews views = new RemoteViews(context.getPackageName(), layout);
views.setViewVisibility(R.id.subtext, View.GONE);
views.setViewVisibility(R.id.text_content, View.VISIBLE);
views.setTextViewText(R.id.text_content, tile.getNotificationContent());
views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_phone_missed);
ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
views.setBoolean(R.id.content_background, "setClipToOutline", true);
return views;
}
private static void ensurePredefinedIconVisibleOnSmallView(RemoteViews views, int layoutSize) {
if (layoutSize == SMALL_LAYOUT) {
views.setViewVisibility(R.id.name, View.GONE);
views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
}
}
private static RemoteViews createNotificationRemoteViews(Context context,
PeopleSpaceTile tile) {
RemoteViews views = new RemoteViews(
context.getPackageName(), R.layout.people_space_notification_content_tile);
PeopleSpaceTile tile, int layoutSize) {
int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
: R.layout.people_space_small_avatar_tile;
RemoteViews views = new RemoteViews(context.getPackageName(), layout);
if (layoutSize != MEDIUM_LAYOUT) {
views.setViewVisibility(R.id.name, View.GONE);
views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
}
Uri image = tile.getNotificationDataUri();
ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
if (image != null) {
// TODO: Use NotificationInlineImageCache
views.setImageViewUri(R.id.image, image);
views.setViewVisibility(R.id.content_background, View.VISIBLE);
views.setBoolean(R.id.content_background, "setClipToOutline", true);
views.setViewVisibility(R.id.content, View.GONE);
views.setViewVisibility(R.id.text_content, View.GONE);
views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_photo_camera);
} else {
CharSequence content = tile.getNotificationContent();
views = setPunctuationRemoteViewsFields(views, content);
views.setTextViewText(R.id.content, content);
views.setViewVisibility(R.id.content, View.VISIBLE);
views.setTextViewText(R.id.text_content, tile.getNotificationContent());
// TODO: Measure max lines from height.
views.setInt(R.id.text_content, "setMaxLines", 2);
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
int primaryTextColor = context.getColor(typedValue.resourceId);
views.setInt(R.id.text_content, "setTextColor", primaryTextColor);
views.setViewVisibility(R.id.text_content, View.VISIBLE);
views.setViewVisibility(R.id.content_background, View.GONE);
views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
}
// TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile.
views.setTextViewText(R.id.subtext, PeopleSpaceUtils.getLastInteractionString(
context, tile.getLastInteractionTimestamp()));
views.setViewVisibility(R.id.subtext, View.GONE);
return views;
}
private static RemoteViews createLastInteractionRemoteViews(Context context,
PeopleSpaceTile tile) {
RemoteViews views = new RemoteViews(
context.getPackageName(), R.layout.people_space_large_avatar_tile);
PeopleSpaceTile tile, int layoutSize) {
int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
: R.layout.people_space_large_avatar_tile;
RemoteViews views = new RemoteViews(context.getPackageName(), layout);
if (layoutSize == SMALL_LAYOUT) {
views.setViewVisibility(R.id.name, View.VISIBLE);
views.setViewVisibility(R.id.predefined_icon, View.GONE);
}
String status = PeopleSpaceUtils.getLastInteractionString(
context, tile.getLastInteractionTimestamp());
views.setTextViewText(R.id.last_interaction, status);
@@ -900,18 +992,22 @@ public class PeopleSpaceUtils {
removeBirthdayStatusIfPresent(appWidgetManager, context, storedTile, appWidgetId);
}
/** Updates tile in app widget options and the current view. */
public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
Context context, int appWidgetId, PeopleSpaceTile tile) {
AppWidgetOptionsHelper.setPeopleTile(appWidgetManager, appWidgetId, tile);
private static void updateAppWidgetViews(AppWidgetManager appWidgetManager,
Context context, int appWidgetId, PeopleSpaceTile tile, Bundle options) {
if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName());
RemoteViews views = createRemoteViews(context, tile, appWidgetId);
RemoteViews views = createRemoteViews(context, tile, appWidgetId, options);
// Tell the AppWidgetManager to perform an update on the current app widget.
appWidgetManager.updateAppWidget(appWidgetId, views);
}
/** Updates tile in app widget options and the current view. */
public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
Context context, int appWidgetId, PeopleSpaceTile tile) {
Bundle options = AppWidgetOptionsHelper.setPeopleTile(appWidgetManager, appWidgetId, tile);
updateAppWidgetViews(appWidgetManager, context, appWidgetId, tile, options);
}
/**
* Returns lookup keys for all contacts with a birthday today.
*

View File

@@ -0,0 +1,215 @@
/*
* 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.systemui.people;
import android.annotation.ColorInt;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.TypedValue;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.systemui.R;
class PeopleStoryIconFactory extends BaseIconFactory {
private static final int PADDING = 2;
private static final int RING_WIDTH = 2;
private static final int MAX_BADGE_SIZE = 36;
final PackageManager mPackageManager;
final IconDrawableFactory mIconDrawableFactory;
private int mImportantConversationColor;
private int mAccentColor;
private float mDensity;
private float mIconSize;
PeopleStoryIconFactory(Context context, PackageManager pm,
IconDrawableFactory iconDrawableFactory, int iconSizeDp) {
super(context, context.getResources().getConfiguration().densityDpi,
(int) (iconSizeDp * context.getResources().getDisplayMetrics().density));
mDensity = context.getResources().getDisplayMetrics().density;
mIconSize = mDensity * iconSizeDp;
mPackageManager = pm;
mIconDrawableFactory = iconDrawableFactory;
mImportantConversationColor = context.getColor(R.color.important_conversation);
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
mAccentColor = context.getColor(typedValue.resourceId);
}
/**
* Gets the {@link Drawable} that represents the app icon, badged with the work profile icon
* if appropriate.
*/
private Drawable getAppBadge(String packageName, int userId) {
Drawable badge = null;
try {
final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
packageName, PackageManager.GET_META_DATA, userId);
badge = mIconDrawableFactory.getBadgedIcon(appInfo, userId);
} catch (PackageManager.NameNotFoundException e) {
badge = mPackageManager.getDefaultActivityIcon();
}
return badge;
}
/**
* Returns a {@link Drawable} for the entire conversation. The shortcut icon will be badged
* with the launcher icon of the app specified by packageName.
*/
public Drawable getPeopleTileDrawable(Drawable headDrawable, String packageName, int userId,
boolean important, boolean newStory) {
return new PeopleStoryIconDrawable(headDrawable, getAppBadge(packageName, userId),
mIconBitmapSize, mImportantConversationColor, important, mIconSize, mDensity,
mAccentColor, newStory);
}
/**
* Custom drawable which overlays a badge drawable on a head icon (conversation/person avatar),
* with decorations indicating Important conversations and having a New Story.
*/
public static class PeopleStoryIconDrawable extends Drawable {
private float mFullIconSize;
private Drawable mAvatar;
private Drawable mBadgeIcon;
private int mIconSize;
private Paint mPriorityRingPaint;
private boolean mShowImportantRing;
private boolean mShowStoryRing;
private Paint mStoryPaint;
private float mDensity;
PeopleStoryIconDrawable(Drawable avatar,
Drawable badgeIcon,
int iconSize,
@ColorInt int ringColor,
boolean showImportantRing, float fullIconSize, float density,
@ColorInt int accentColor, boolean showStoryRing) {
mAvatar = avatar;
mBadgeIcon = badgeIcon;
mIconSize = iconSize;
mShowImportantRing = showImportantRing;
mPriorityRingPaint = new Paint();
mPriorityRingPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPriorityRingPaint.setColor(ringColor);
mShowStoryRing = showStoryRing;
mStoryPaint = new Paint();
mStoryPaint.setStyle(Paint.Style.STROKE);
mStoryPaint.setColor(accentColor);
mFullIconSize = fullIconSize;
mDensity = density;
}
@Override
public int getIntrinsicWidth() {
return mIconSize;
}
@Override
public int getIntrinsicHeight() {
return mIconSize;
}
@Override
public void draw(Canvas canvas) {
final Rect bounds = getBounds();
final int minBound = Math.min(bounds.height(), bounds.width());
// Scale head icon and app icon to our canvas.
float scale = minBound / mFullIconSize;
int paddingInDp = (int) (PADDING * mDensity);
int ringStrokeWidth = (int) (RING_WIDTH * mDensity);
mPriorityRingPaint.setStrokeWidth(ringStrokeWidth);
mStoryPaint.setStrokeWidth(ringStrokeWidth);
int scaledFullIconSize = (int) (mFullIconSize * scale);
int avatarSize = scaledFullIconSize - (paddingInDp * 2);
if (mAvatar != null) {
int leftAndTopPadding = paddingInDp;
int rightAndBottomPadding = avatarSize + paddingInDp;
if (mShowStoryRing) {
int headCenter = scaledFullIconSize / 2;
canvas.drawCircle(headCenter, headCenter,
getRadius(avatarSize, ringStrokeWidth),
mStoryPaint);
leftAndTopPadding += (ringStrokeWidth + paddingInDp);
rightAndBottomPadding -= (ringStrokeWidth + paddingInDp);
}
mAvatar.setBounds(leftAndTopPadding,
leftAndTopPadding,
rightAndBottomPadding,
rightAndBottomPadding);
mAvatar.draw(canvas);
} else {
Log.w("PeopleStoryIconFactory", "Null avatar icon");
}
// Determine badge size from either the size relative to the head icon, or max size.
int maxBadgeSize = (int) (MAX_BADGE_SIZE * mDensity);
int badgeSizeRelativeToHead = (int) (avatarSize / 2.4);
int badgeSize = Math.min(maxBadgeSize, badgeSizeRelativeToHead);
if (mBadgeIcon != null) {
int leftAndTopPadding = scaledFullIconSize - badgeSize;
int rightAndBottomPadding = scaledFullIconSize;
if (mShowImportantRing) {
int badgeCenter = leftAndTopPadding + (badgeSize / 2);
canvas.drawCircle(badgeCenter, badgeCenter,
getRadius(badgeSize, ringStrokeWidth),
mPriorityRingPaint);
leftAndTopPadding += ringStrokeWidth;
rightAndBottomPadding -= ringStrokeWidth;
}
mBadgeIcon.setBounds(
leftAndTopPadding,
leftAndTopPadding,
rightAndBottomPadding,
rightAndBottomPadding);
mBadgeIcon.draw(canvas);
} else {
Log.w("PeopleStoryIconFactory", "Null badge icon");
}
}
private int getRadius(int circleWidth, int circleStrokeWidth) {
return (circleWidth - circleStrokeWidth) / 2;
}
@Override
public void setAlpha(int alpha) {
// unimplemented
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
// unimplemented
}
@Override
public int getOpacity() {
return 0;
}
}
}

View File

@@ -37,15 +37,16 @@ public class AppWidgetOptionsHelper {
public static final String OPTIONS_PEOPLE_TILE = "options_people_tile";
/** Sets {@link PeopleSpaceTile} in AppWidgetOptions. */
public static void setPeopleTile(AppWidgetManager appWidgetManager, int appWidgetId,
public static Bundle setPeopleTile(AppWidgetManager appWidgetManager, int appWidgetId,
PeopleSpaceTile tile) {
Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
if (tile == null) {
if (DEBUG) Log.d(TAG, "Requested to store null tile");
return;
return options;
}
Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
options.putParcelable(OPTIONS_PEOPLE_TILE, tile);
appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
return options;
}
/** Gets {@link PeopleSpaceTile} from AppWidgetOptions. */

View File

@@ -217,8 +217,7 @@ public class PeopleSpaceWidgetManager {
.setUserName(info.getLabel())
.setUserIcon(
PeopleSpaceTile.convertDrawableToIcon(mLauncherApps.getShortcutIconDrawable(
info, 0))
)
info, 0)))
.setContactUri(uri)
.setStatuses(conversation.getStatuses())
.setLastInteractionTimestamp(conversation.getLastEventTimestamp())
@@ -250,9 +249,12 @@ public class PeopleSpaceWidgetManager {
}
storedTile = storedTile
.toBuilder()
// Reset notification content.
.setNotificationKey(null)
.setNotificationContent(null)
.setNotificationDataUri(null)
// Reset missed calls category.
.setNotificationCategory(null)
.build();
}
updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, appWidgetId, storedTile);
@@ -333,6 +335,8 @@ public class PeopleSpaceWidgetManager {
addNewWidget(appWidgetId, optionsKey);
AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManager, appWidgetId);
}
// Update views for new widget dimensions.
updateWidgets(new int[]{appWidgetId});
}
/** Adds{@code tile} mapped to {@code appWidgetId}. */

View File

@@ -21,15 +21,16 @@ import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
import static android.app.people.ConversationStatus.ACTIVITY_GAME;
import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
import static com.android.systemui.people.PeopleSpaceUtils.REQUIRED_WIDTH_FOR_MEDIUM;
import static com.android.systemui.people.PeopleSpaceUtils.getPeopleTileFromPersistentStorage;
import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -59,6 +60,8 @@ import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -226,6 +229,8 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
@Mock
private NotificationEntryManager mNotificationEntryManager;
private Bundle mOptions;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -233,12 +238,12 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
Bundle options = new Bundle();
options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
mOptions = new Bundle();
mOptions.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT)))
.thenReturn(options);
.thenReturn(mOptions);
when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
.thenReturn(new Bundle());
@@ -252,6 +257,10 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
mContext.getString(R.string.over_timestamp));
Configuration configuration = mock(Configuration.class);
Resources resources = mock(Resources.class);
when(mMockContext.getResources()).thenReturn(resources);
when(resources.getConfiguration()).thenReturn(configuration);
when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
when(mNotificationEntryManager.getVisibleNotifications())
.thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
@@ -665,7 +674,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
@Test
public void testCreateRemoteViewsWithLastInteractionTime() {
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
PERSON_TILE_WITHOUT_NOTIFICATION, 0);
PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -676,13 +685,22 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
// No availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.GONE, availability.getVisibility());
// No new story.
View personIcon = result.findViewById(R.id.person_icon_only);
View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
// Shows person icon.
View personIcon = result.findViewById(R.id.person_icon);
assertEquals(View.VISIBLE, personIcon.getVisibility());
assertEquals(View.GONE, personIconWithStory.getVisibility());
// No status.
assertThat((View) result.findViewById(R.id.status)).isNull();
assertThat((View) result.findViewById(R.id.text_content)).isNull();
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions);
View smallResult = smallView.apply(mContext, null);
// Show name over predefined icon.
assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
// Shows person icon.
assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
}
@Test
@@ -694,7 +712,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
ACTIVITY_GAME).build())).build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
tileWithAvailabilityAndNewStory, 0);
tileWithAvailabilityAndNewStory, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -705,13 +723,23 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.VISIBLE, availability.getVisibility());
// Has new story.
View personIcon = result.findViewById(R.id.person_icon_only);
View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
assertEquals(View.GONE, personIcon.getVisibility());
assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
// Has person icon.
View personIcon = result.findViewById(R.id.person_icon);
assertEquals(View.VISIBLE, personIcon.getVisibility());
// No status.
assertThat((View) result.findViewById(R.id.status)).isNull();
assertThat((View) result.findViewById(R.id.text_content)).isNull();
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
tileWithAvailabilityAndNewStory, 0, mOptions);
View smallResult = smallView.apply(mContext, null);
// Show name rather than game type.
assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
// Has person icon.
assertEquals(View.VISIBLE,
smallResult.findViewById(R.id.person_icon).getVisibility());
}
@Test
@@ -723,7 +751,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
ACTIVITY_BIRTHDAY).build())).build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
tileWithStatusTemplate, 0);
tileWithStatusTemplate, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -731,14 +759,25 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.VISIBLE, availability.getVisibility());
// Has new story.
View personIcon = result.findViewById(R.id.person_icon_only);
View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
assertEquals(View.GONE, personIcon.getVisibility());
assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
// Has person icon.
View personIcon = result.findViewById(R.id.person_icon);
assertEquals(View.VISIBLE, personIcon.getVisibility());
// Has status text from backup text.
TextView statusContent = (TextView) result.findViewById(R.id.status);
TextView statusContent = (TextView) result.findViewById(R.id.text_content);
assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
tileWithStatusTemplate, 0, mOptions);
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
assertEquals(View.VISIBLE,
smallResult.findViewById(R.id.predefined_icon).getVisibility());
// Has person icon.
assertEquals(View.VISIBLE,
smallResult.findViewById(R.id.person_icon).getVisibility());
}
@Test
@@ -748,7 +787,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
Arrays.asList(GAME_STATUS,
NEW_STORY_WITH_AVAILABILITY)).build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
tileWithStatusTemplate, 0);
tileWithStatusTemplate, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -756,14 +795,25 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.VISIBLE, availability.getVisibility());
// Has new story.
View personIcon = result.findViewById(R.id.person_icon_only);
View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
assertEquals(View.GONE, personIcon.getVisibility());
assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
// Has person icon.
View personIcon = result.findViewById(R.id.person_icon);
assertEquals(View.VISIBLE, personIcon.getVisibility());
// Has status.
TextView statusContent = (TextView) result.findViewById(R.id.status);
TextView statusContent = (TextView) result.findViewById(R.id.text_content);
assertEquals(statusContent.getText(), GAME_DESCRIPTION);
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
tileWithStatusTemplate, 0, mOptions);
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
assertEquals(View.VISIBLE,
smallResult.findViewById(R.id.predefined_icon).getVisibility());
// Has person icon.
assertEquals(View.VISIBLE,
smallResult.findViewById(R.id.person_icon).getVisibility());
}
@Test
@@ -774,7 +824,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
.setNotificationContent(MISSED_CALL)
.build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
tileWithMissedCallNotification, 0);
tileWithMissedCallNotification, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -782,16 +832,25 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.GONE, availability.getVisibility());
// Has new story.
View personIcon = result.findViewById(R.id.person_icon_only);
View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
// Has person icon.
View personIcon = result.findViewById(R.id.person_icon);
assertEquals(View.VISIBLE, personIcon.getVisibility());
assertEquals(View.GONE, personIconWithStory.getVisibility());
// Has status.
TextView statusContent = (TextView) result.findViewById(R.id.status);
// Has missed call notification content.
TextView statusContent = (TextView) result.findViewById(R.id.text_content);
assertEquals(statusContent.getText(), MISSED_CALL);
}
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
tileWithMissedCallNotification, 0, mOptions);
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
assertEquals(View.VISIBLE,
smallResult.findViewById(R.id.predefined_icon).getVisibility());
// Has person icon.
assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
}
@Test
public void testCreateRemoteViewsWithNotificationTemplate() {
@@ -800,24 +859,35 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase {
.setStatuses(Arrays.asList(GAME_STATUS,
NEW_STORY_WITH_AVAILABILITY)).build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
tileWithStatusAndNotification, 0);
tileWithStatusAndNotification, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
assertEquals(name.getText(), NAME);
TextView subtext = (TextView) result.findViewById(R.id.subtext);
assertTrue(subtext.getText().toString().contains("weeks ago"));
assertEquals(View.GONE, subtext.getVisibility());
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.VISIBLE, availability.getVisibility());
// Has new story.
View personIcon = result.findViewById(R.id.person_icon_only);
View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
assertEquals(View.GONE, personIcon.getVisibility());
assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
// Has person icon.
View personIcon = result.findViewById(R.id.person_icon);
assertEquals(View.VISIBLE, personIcon.getVisibility());
// Has notification content.
TextView statusContent = (TextView) result.findViewById(R.id.content);
TextView statusContent = (TextView) result.findViewById(R.id.text_content);
assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
tileWithStatusAndNotification, 0, mOptions);
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
assertEquals(View.VISIBLE,
smallResult.findViewById(R.id.predefined_icon).getVisibility());
// Has person icon.
assertEquals(View.VISIBLE,
smallResult.findViewById(R.id.person_icon).getVisibility());
}
@Test

View File

@@ -626,14 +626,6 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
mManager.updateWidgets(widgetIdsArray);
mClock.advanceTime(MIN_LINGER_DURATION);
// If we had to fetch Tile from persistent storage, we want to make sure we write it to
// options.
verify(mAppWidgetManager, times(1))
.updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
mBundleArgumentCaptor.capture());
Bundle bundle = mBundleArgumentCaptor.getValue();
PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
assertThat(tile.getId()).isEqualTo(SHORTCUT_ID);
verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
any());
}