Merge changes from topics "people-space-screenshot", "screenshot-hardware" into tm-qpr-dev am: 9fcf0aac75
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19268008 Change-Id: I6471899f2022e003aafde4d4965cd964ad9033d7 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
committed by
Automerger Merge Worker
commit
55a4027966
@@ -110,6 +110,7 @@ android_library {
|
|||||||
"androidx.arch.core_core-runtime",
|
"androidx.arch.core_core-runtime",
|
||||||
"androidx.lifecycle_lifecycle-common-java8",
|
"androidx.lifecycle_lifecycle-common-java8",
|
||||||
"androidx.lifecycle_lifecycle-extensions",
|
"androidx.lifecycle_lifecycle-extensions",
|
||||||
|
"androidx.lifecycle_lifecycle-runtime-ktx",
|
||||||
"androidx.dynamicanimation_dynamicanimation",
|
"androidx.dynamicanimation_dynamicanimation",
|
||||||
"androidx-constraintlayout_constraintlayout",
|
"androidx-constraintlayout_constraintlayout",
|
||||||
"androidx.exifinterface_exifinterface",
|
"androidx.exifinterface_exifinterface",
|
||||||
@@ -218,6 +219,7 @@ android_library {
|
|||||||
"androidx.arch.core_core-runtime",
|
"androidx.arch.core_core-runtime",
|
||||||
"androidx.lifecycle_lifecycle-common-java8",
|
"androidx.lifecycle_lifecycle-common-java8",
|
||||||
"androidx.lifecycle_lifecycle-extensions",
|
"androidx.lifecycle_lifecycle-extensions",
|
||||||
|
"androidx.lifecycle_lifecycle-runtime-ktx",
|
||||||
"androidx.dynamicanimation_dynamicanimation",
|
"androidx.dynamicanimation_dynamicanimation",
|
||||||
"androidx-constraintlayout_constraintlayout",
|
"androidx-constraintlayout_constraintlayout",
|
||||||
"androidx.exifinterface_exifinterface",
|
"androidx.exifinterface_exifinterface",
|
||||||
|
|||||||
@@ -13,103 +13,11 @@
|
|||||||
~ See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<LinearLayout
|
<FrameLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
android:id="@+id/container"
|
||||||
android:id="@+id/top_level"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
<!-- The content of people_space_activity_(no|with)_conversations.xml will be added here at
|
||||||
android:padding="8dp">
|
runtime depending on the number of conversations to show. -->
|
||||||
<TextView
|
</FrameLayout>
|
||||||
android:id="@+id/select_conversation_title"
|
|
||||||
android:text="@string/select_conversation_title"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:textSize="24sp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/select_conversation"
|
|
||||||
android:text="@string/select_conversation_text"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:paddingVertical="24dp"
|
|
||||||
android:paddingHorizontal="48dp"/>
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:id="@+id/scroll_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/scroll_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/priority"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="35dp">
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/priority_header"
|
|
||||||
android:text="@string/priority_conversations"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
|
|
||||||
android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/priority_tiles"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_bg_full_large_radius"
|
|
||||||
android:clipToOutline="true">
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/recent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/recent_header"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text="@string/recent_conversations"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
|
|
||||||
android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/recent_tiles"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_bg_full_large_radius"
|
|
||||||
android:clipToOutline="true">
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||||
android:id="@+id/top_level"
|
android:id="@+id/top_level_no_conversations"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="24dp"
|
android:padding="24dp"
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2022 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"
|
||||||
|
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||||
|
android:id="@+id/top_level_with_conversations"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/select_conversation_title"
|
||||||
|
android:text="@string/select_conversation_title"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="24sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/select_conversation"
|
||||||
|
android:text="@string/select_conversation_text"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="24dp"
|
||||||
|
android:paddingHorizontal="48dp"/>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/scroll_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/scroll_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/priority"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="35dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/priority_header"
|
||||||
|
android:text="@string/priority_conversations"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
|
||||||
|
android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/priority_tiles"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_bg_full_large_radius"
|
||||||
|
android:clipToOutline="true">
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/recent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recent_header"
|
||||||
|
android:gravity="start"
|
||||||
|
android:text="@string/recent_conversations"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
|
||||||
|
android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/recent_tiles"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_bg_full_large_radius"
|
||||||
|
android:clipToOutline="true">
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
</LinearLayout>
|
||||||
@@ -37,8 +37,8 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/tile_view_person_icon"
|
android:id="@+id/tile_view_person_icon"
|
||||||
android:layout_width="52dp"
|
android:layout_width="@dimen/avatar_size_for_medium"
|
||||||
android:layout_height="52dp" />
|
android:layout_height="@dimen/avatar_size_for_medium" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import com.android.systemui.lowlightclock.LowLightClockController;
|
|||||||
import com.android.systemui.media.dagger.MediaProjectionModule;
|
import com.android.systemui.media.dagger.MediaProjectionModule;
|
||||||
import com.android.systemui.model.SysUiState;
|
import com.android.systemui.model.SysUiState;
|
||||||
import com.android.systemui.navigationbar.NavigationBarComponent;
|
import com.android.systemui.navigationbar.NavigationBarComponent;
|
||||||
|
import com.android.systemui.people.PeopleModule;
|
||||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
|
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
|
||||||
import com.android.systemui.privacy.PrivacyModule;
|
import com.android.systemui.privacy.PrivacyModule;
|
||||||
import com.android.systemui.recents.Recents;
|
import com.android.systemui.recents.Recents;
|
||||||
@@ -124,6 +125,7 @@ import dagger.Provides;
|
|||||||
LogModule.class,
|
LogModule.class,
|
||||||
MediaProjectionModule.class,
|
MediaProjectionModule.class,
|
||||||
PeopleHubModule.class,
|
PeopleHubModule.class,
|
||||||
|
PeopleModule.class,
|
||||||
PluginModule.class,
|
PluginModule.class,
|
||||||
PrivacyModule.class,
|
PrivacyModule.class,
|
||||||
QsFrameTranslateModule.class,
|
QsFrameTranslateModule.class,
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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 com.android.systemui.people.data.repository.PeopleTileRepository
|
||||||
|
import com.android.systemui.people.data.repository.PeopleTileRepositoryImpl
|
||||||
|
import com.android.systemui.people.data.repository.PeopleWidgetRepository
|
||||||
|
import com.android.systemui.people.data.repository.PeopleWidgetRepositoryImpl
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
|
||||||
|
/** Dagger module to provide/bind people space dependencies. */
|
||||||
|
@Module
|
||||||
|
interface PeopleModule {
|
||||||
|
@Binds fun bindTileRepository(impl: PeopleTileRepositoryImpl): PeopleTileRepository
|
||||||
|
|
||||||
|
@Binds fun bindWidgetRepository(impl: PeopleWidgetRepositoryImpl): PeopleWidgetRepository
|
||||||
|
}
|
||||||
@@ -19,144 +19,52 @@ package com.android.systemui.people;
|
|||||||
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
|
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
|
||||||
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
|
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
|
||||||
|
|
||||||
import static com.android.systemui.people.PeopleTileViewHelper.getPersonIconBitmap;
|
|
||||||
import static com.android.systemui.people.PeopleTileViewHelper.getSizeInDp;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.people.PeopleSpaceTile;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Outline;
|
|
||||||
import android.graphics.drawable.GradientDrawable;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewOutlineProvider;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
|
|
||||||
import com.android.systemui.R;
|
import androidx.activity.ComponentActivity;
|
||||||
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import com.android.systemui.people.widget.PeopleTileKey;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import com.android.systemui.people.ui.view.PeopleViewBinder;
|
||||||
import java.util.List;
|
import com.android.systemui.people.ui.viewmodel.PeopleViewModel;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/** People Tile Widget configuration activity that shows the user their conversation tiles. */
|
/** People Tile Widget configuration activity that shows the user their conversation tiles. */
|
||||||
public class PeopleSpaceActivity extends Activity {
|
public class PeopleSpaceActivity extends ComponentActivity {
|
||||||
|
|
||||||
private static final String TAG = "PeopleSpaceActivity";
|
private static final String TAG = "PeopleSpaceActivity";
|
||||||
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
|
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
|
||||||
|
|
||||||
private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
|
private final PeopleViewModel.Factory mViewModelFactory;
|
||||||
private Context mContext;
|
private PeopleViewModel mViewModel;
|
||||||
private int mAppWidgetId;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PeopleSpaceActivity(PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
|
public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) {
|
||||||
super();
|
super();
|
||||||
mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
|
mViewModelFactory = viewModelFactory;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
mContext = getApplicationContext();
|
|
||||||
mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID,
|
|
||||||
INVALID_APPWIDGET_ID);
|
|
||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
}
|
mViewModel = new ViewModelProvider(this, mViewModelFactory).get(PeopleViewModel.class);
|
||||||
|
|
||||||
/** Builds the conversation selection activity. */
|
// Update the widget ID coming from the intent.
|
||||||
private void buildActivity() {
|
int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
|
||||||
List<PeopleSpaceTile> priorityTiles = new ArrayList<>();
|
mViewModel.onWidgetIdChanged(widgetId);
|
||||||
List<PeopleSpaceTile> recentTiles = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
priorityTiles = mPeopleSpaceWidgetManager.getPriorityTiles();
|
|
||||||
recentTiles = mPeopleSpaceWidgetManager.getRecentTiles();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Couldn't retrieve conversations", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no conversations, render activity without conversations
|
ViewGroup view = PeopleViewBinder.create(this);
|
||||||
if (recentTiles.isEmpty() && priorityTiles.isEmpty()) {
|
PeopleViewBinder.bind(view, mViewModel, /* lifecycleOwner= */ this,
|
||||||
setContentView(R.layout.people_space_activity_no_conversations);
|
() -> {
|
||||||
|
finishActivity();
|
||||||
// The Tile preview has colorBackground as its background. Change it so it's different
|
return null;
|
||||||
// than the activity's background.
|
});
|
||||||
LinearLayout item = findViewById(android.R.id.background);
|
setContentView(view);
|
||||||
GradientDrawable shape = (GradientDrawable) item.getBackground();
|
|
||||||
final TypedArray ta = mContext.getTheme().obtainStyledAttributes(
|
|
||||||
new int[]{com.android.internal.R.attr.colorSurface});
|
|
||||||
shape.setColor(ta.getColor(0, Color.WHITE));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentView(R.layout.people_space_activity);
|
|
||||||
setTileViews(R.id.priority, R.id.priority_tiles, priorityTiles);
|
|
||||||
setTileViews(R.id.recent, R.id.recent_tiles, recentTiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ViewOutlineProvider mViewOutlineProvider = new ViewOutlineProvider() {
|
|
||||||
@Override
|
|
||||||
public void getOutline(View view, Outline outline) {
|
|
||||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
|
|
||||||
mContext.getResources().getDimension(R.dimen.people_space_widget_radius));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Sets a {@link PeopleSpaceTileView}s for each conversation. */
|
|
||||||
private void setTileViews(int viewId, int tilesId, List<PeopleSpaceTile> tiles) {
|
|
||||||
if (tiles.isEmpty()) {
|
|
||||||
LinearLayout view = findViewById(viewId);
|
|
||||||
view.setVisibility(View.GONE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewGroup layout = findViewById(tilesId);
|
|
||||||
layout.setClipToOutline(true);
|
|
||||||
layout.setOutlineProvider(mViewOutlineProvider);
|
|
||||||
for (int i = 0; i < tiles.size(); ++i) {
|
|
||||||
PeopleSpaceTile tile = tiles.get(i);
|
|
||||||
PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext,
|
|
||||||
layout, tile.getId(), i == (tiles.size() - 1));
|
|
||||||
setTileView(tileView, tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets {@code tileView} with the data in {@code conversation}. */
|
|
||||||
private void setTileView(PeopleSpaceTileView tileView, PeopleSpaceTile tile) {
|
|
||||||
try {
|
|
||||||
if (tile.getUserName() != null) {
|
|
||||||
tileView.setName(tile.getUserName().toString());
|
|
||||||
}
|
|
||||||
tileView.setPersonIcon(getPersonIconBitmap(mContext, tile,
|
|
||||||
getSizeInDp(mContext, R.dimen.avatar_size_for_medium,
|
|
||||||
mContext.getResources().getDisplayMetrics().density)));
|
|
||||||
|
|
||||||
PeopleTileKey key = new PeopleTileKey(tile);
|
|
||||||
tileView.setOnClickListener(v -> storeWidgetConfiguration(tile, key));
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Couldn't retrieve shortcut information", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Stores the user selected configuration for {@code mAppWidgetId}. */
|
|
||||||
private void storeWidgetConfiguration(PeopleSpaceTile tile, PeopleTileKey key) {
|
|
||||||
if (PeopleSpaceUtils.DEBUG) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
|
|
||||||
+ tile.getId() + " for widget ID: "
|
|
||||||
+ mAppWidgetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mPeopleSpaceWidgetManager.addNewWidget(mAppWidgetId, key);
|
|
||||||
finishActivity();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Finish activity with a successful widget configuration result. */
|
/** Finish activity with a successful widget configuration result. */
|
||||||
@@ -169,19 +77,13 @@ public class PeopleSpaceActivity extends Activity {
|
|||||||
/** Finish activity without choosing a widget. */
|
/** Finish activity without choosing a widget. */
|
||||||
public void dismissActivity(View v) {
|
public void dismissActivity(View v) {
|
||||||
if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
|
if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setActivityResult(int result) {
|
private void setActivityResult(int result) {
|
||||||
Intent resultValue = new Intent();
|
Intent resultValue = new Intent();
|
||||||
resultValue.putExtra(EXTRA_APPWIDGET_ID, mAppWidgetId);
|
resultValue.putExtra(EXTRA_APPWIDGET_ID, mViewModel.getAppWidgetId().getValue());
|
||||||
setResult(result, resultValue);
|
setResult(result, resultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
// Refresh tile views to sync new conversations.
|
|
||||||
buildActivity();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.graphics.Rect;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.IconDrawableFactory;
|
import android.util.IconDrawableFactory;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
|
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
||||||
|
|
||||||
@@ -52,16 +53,15 @@ class PeopleStoryIconFactory implements AutoCloseable {
|
|||||||
|
|
||||||
PeopleStoryIconFactory(Context context, PackageManager pm,
|
PeopleStoryIconFactory(Context context, PackageManager pm,
|
||||||
IconDrawableFactory iconDrawableFactory, int iconSizeDp) {
|
IconDrawableFactory iconDrawableFactory, int iconSizeDp) {
|
||||||
context.setTheme(android.R.style.Theme_DeviceDefault_DayNight);
|
mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_DayNight);
|
||||||
mIconBitmapSize = (int) (iconSizeDp * context.getResources().getDisplayMetrics().density);
|
mIconBitmapSize = (int) (iconSizeDp * mContext.getResources().getDisplayMetrics().density);
|
||||||
mDensity = context.getResources().getDisplayMetrics().density;
|
mDensity = mContext.getResources().getDisplayMetrics().density;
|
||||||
mIconSize = mDensity * iconSizeDp;
|
mIconSize = mDensity * iconSizeDp;
|
||||||
mPackageManager = pm;
|
mPackageManager = pm;
|
||||||
mIconDrawableFactory = iconDrawableFactory;
|
mIconDrawableFactory = iconDrawableFactory;
|
||||||
mImportantConversationColor = context.getColor(R.color.important_conversation);
|
mImportantConversationColor = mContext.getColor(R.color.important_conversation);
|
||||||
mAccentColor = Utils.getColorAttr(context,
|
mAccentColor = Utils.getColorAttr(mContext,
|
||||||
com.android.internal.R.attr.colorAccentPrimaryVariant).getDefaultColor();
|
com.android.internal.R.attr.colorAccentPrimaryVariant).getDefaultColor();
|
||||||
mContext = context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import androidx.core.math.MathUtils;
|
|||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.people.data.model.PeopleTileModel;
|
||||||
import com.android.systemui.people.widget.LaunchConversationActivity;
|
import com.android.systemui.people.widget.LaunchConversationActivity;
|
||||||
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
|
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
|
||||||
import com.android.systemui.people.widget.PeopleTileKey;
|
import com.android.systemui.people.widget.PeopleTileKey;
|
||||||
@@ -299,7 +300,8 @@ public class PeopleTileViewHelper {
|
|||||||
return createLastInteractionRemoteViews();
|
return createLastInteractionRemoteViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) {
|
/** Whether the conversation associated with {@code tile} can bypass DND. */
|
||||||
|
public static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) {
|
||||||
if (tile == null) return false;
|
if (tile == null) return false;
|
||||||
|
|
||||||
int notificationPolicyState = tile.getNotificationPolicyState();
|
int notificationPolicyState = tile.getNotificationPolicyState();
|
||||||
@@ -536,7 +538,8 @@ public class PeopleTileViewHelper {
|
|||||||
return views;
|
return views;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean getHasNewStory(PeopleSpaceTile tile) {
|
/** Whether {@code tile} has a new story. */
|
||||||
|
public static boolean getHasNewStory(PeopleSpaceTile tile) {
|
||||||
return tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
|
return tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
|
||||||
c -> c.getActivity() == ACTIVITY_NEW_STORY);
|
c -> c.getActivity() == ACTIVITY_NEW_STORY);
|
||||||
}
|
}
|
||||||
@@ -1250,16 +1253,24 @@ public class PeopleTileViewHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a bitmap with the user icon and package icon. */
|
/** Returns a bitmap with the user icon and package icon. */
|
||||||
public static Bitmap getPersonIconBitmap(Context context, PeopleSpaceTile tile,
|
public static Bitmap getPersonIconBitmap(Context context, PeopleTileModel tile,
|
||||||
int maxAvatarSize) {
|
int maxAvatarSize) {
|
||||||
boolean hasNewStory = getHasNewStory(tile);
|
return getPersonIconBitmap(context, maxAvatarSize, tile.getHasNewStory(),
|
||||||
return getPersonIconBitmap(context, tile, maxAvatarSize, hasNewStory);
|
tile.getUserIcon(), tile.getKey().getPackageName(), tile.getKey().getUserId(),
|
||||||
|
tile.isImportant(), tile.isDndBlocking());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a bitmap with the user icon and package icon. */
|
/** Returns a bitmap with the user icon and package icon. */
|
||||||
private static Bitmap getPersonIconBitmap(
|
private static Bitmap getPersonIconBitmap(
|
||||||
Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) {
|
Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) {
|
||||||
Icon icon = tile.getUserIcon();
|
return getPersonIconBitmap(context, maxAvatarSize, hasNewStory, tile.getUserIcon(),
|
||||||
|
tile.getPackageName(), getUserId(tile),
|
||||||
|
tile.isImportantConversation(), isDndBlockingTileData(tile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Bitmap getPersonIconBitmap(
|
||||||
|
Context context, int maxAvatarSize, boolean hasNewStory, Icon icon, String packageName,
|
||||||
|
int userId, boolean importantConversation, boolean dndBlockingTileData) {
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge).mutate();
|
Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge).mutate();
|
||||||
placeholder.setColorFilter(getDisabledColorFilter());
|
placeholder.setColorFilter(getDisabledColorFilter());
|
||||||
@@ -1272,10 +1283,10 @@ public class PeopleTileViewHelper {
|
|||||||
RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
|
RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
|
||||||
context.getResources(), icon.getBitmap());
|
context.getResources(), icon.getBitmap());
|
||||||
Drawable personDrawable = storyIcon.getPeopleTileDrawable(roundedDrawable,
|
Drawable personDrawable = storyIcon.getPeopleTileDrawable(roundedDrawable,
|
||||||
tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
|
packageName, userId, importantConversation,
|
||||||
hasNewStory);
|
hasNewStory);
|
||||||
|
|
||||||
if (isDndBlockingTileData(tile)) {
|
if (dndBlockingTileData) {
|
||||||
personDrawable.setColorFilter(getDisabledColorFilter());
|
personDrawable.setColorFilter(getDisabledColorFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.data.model
|
||||||
|
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import com.android.systemui.people.widget.PeopleTileKey
|
||||||
|
|
||||||
|
/** Models a tile/conversation. */
|
||||||
|
data class PeopleTileModel(
|
||||||
|
val key: PeopleTileKey,
|
||||||
|
val username: String,
|
||||||
|
val userIcon: Icon,
|
||||||
|
val hasNewStory: Boolean,
|
||||||
|
val isImportant: Boolean,
|
||||||
|
val isDndBlocking: Boolean,
|
||||||
|
)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.data.repository
|
||||||
|
|
||||||
|
import android.app.people.PeopleSpaceTile
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.people.PeopleTileViewHelper
|
||||||
|
import com.android.systemui.people.data.model.PeopleTileModel
|
||||||
|
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
|
||||||
|
import com.android.systemui.people.widget.PeopleTileKey
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/** A Repository to fetch the current tiles/conversations. */
|
||||||
|
// TODO(b/238993727): Make the tiles API reactive.
|
||||||
|
interface PeopleTileRepository {
|
||||||
|
/* The current priority tiles. */
|
||||||
|
fun priorityTiles(): List<PeopleTileModel>
|
||||||
|
|
||||||
|
/* The current recent tiles. */
|
||||||
|
fun recentTiles(): List<PeopleTileModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
@SysUISingleton
|
||||||
|
class PeopleTileRepositoryImpl
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
|
||||||
|
) : PeopleTileRepository {
|
||||||
|
override fun priorityTiles(): List<PeopleTileModel> {
|
||||||
|
return peopleSpaceWidgetManager.priorityTiles.map { it.toModel() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun recentTiles(): List<PeopleTileModel> {
|
||||||
|
return peopleSpaceWidgetManager.recentTiles.map { it.toModel() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PeopleSpaceTile.toModel(): PeopleTileModel {
|
||||||
|
return PeopleTileModel(
|
||||||
|
PeopleTileKey(this),
|
||||||
|
userName.toString(),
|
||||||
|
userIcon,
|
||||||
|
PeopleTileViewHelper.getHasNewStory(this),
|
||||||
|
isImportantConversation,
|
||||||
|
PeopleTileViewHelper.isDndBlockingTileData(this),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.data.repository
|
||||||
|
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
|
||||||
|
import com.android.systemui.people.widget.PeopleTileKey
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
interface PeopleWidgetRepository {
|
||||||
|
/**
|
||||||
|
* Bind the widget with ID [widgetId] to the tile keyed by [tileKey].
|
||||||
|
*
|
||||||
|
* If there is already a widget with [widgetId], this existing widget will be reconfigured and
|
||||||
|
* associated to this tile. If there is no widget with [widgetId], a new one will be created.
|
||||||
|
*/
|
||||||
|
fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SysUISingleton
|
||||||
|
class PeopleWidgetRepositoryImpl
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
|
||||||
|
) : PeopleWidgetRepository {
|
||||||
|
override fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) {
|
||||||
|
peopleSpaceWidgetManager.addNewWidget(widgetId, tileKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.ui.view
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Outline
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewOutlineProvider
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.Lifecycle.State.CREATED
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.people.PeopleSpaceTileView
|
||||||
|
import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
|
||||||
|
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/** A ViewBinder for [PeopleViewModel]. */
|
||||||
|
object PeopleViewBinder {
|
||||||
|
private const val TAG = "PeopleSpaceViewBinder"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists.
|
||||||
|
*/
|
||||||
|
private val ViewOutlineProvider =
|
||||||
|
object : ViewOutlineProvider() {
|
||||||
|
override fun getOutline(view: View, outline: Outline) {
|
||||||
|
outline.setRoundRect(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
view.width,
|
||||||
|
view.height,
|
||||||
|
view.context.resources.getDimension(R.dimen.people_space_widget_radius),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a [View] that can later be [bound][bind] to a [PeopleViewModel]. */
|
||||||
|
@JvmStatic
|
||||||
|
fun create(context: Context): ViewGroup {
|
||||||
|
return LayoutInflater.from(context)
|
||||||
|
.inflate(R.layout.people_space_activity, /* root= */ null) as ViewGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Bind [view] to [viewModel]. */
|
||||||
|
@JvmStatic
|
||||||
|
fun bind(
|
||||||
|
view: ViewGroup,
|
||||||
|
viewModel: PeopleViewModel,
|
||||||
|
lifecycleOwner: LifecycleOwner,
|
||||||
|
onFinish: () -> Unit,
|
||||||
|
) {
|
||||||
|
// Call [onFinish] this activity when the ViewModel tells us so.
|
||||||
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
|
lifecycleOwner.repeatOnLifecycle(CREATED) {
|
||||||
|
viewModel.isFinished.collect { isFinished ->
|
||||||
|
if (isFinished) {
|
||||||
|
viewModel.clearIsFinished()
|
||||||
|
onFinish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start collecting the UI data once the Activity is STARTED.
|
||||||
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
|
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
combine(
|
||||||
|
viewModel.priorityTiles,
|
||||||
|
viewModel.recentTiles,
|
||||||
|
) { priority, recent ->
|
||||||
|
priority to recent
|
||||||
|
}
|
||||||
|
.collect { (priorityTiles, recentTiles) ->
|
||||||
|
if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
|
||||||
|
setConversationsContent(
|
||||||
|
view,
|
||||||
|
priorityTiles,
|
||||||
|
recentTiles,
|
||||||
|
viewModel::onTileClicked,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setNoConversationsContent(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to refresh the tiles/conversations when the Activity is resumed, so that it
|
||||||
|
// updates them when going back to the Activity after leaving it.
|
||||||
|
lifecycleOwner.lifecycleScope.launch {
|
||||||
|
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
viewModel.onTileRefreshRequested()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setNoConversationsContent(view: ViewGroup) {
|
||||||
|
// This should never happen.
|
||||||
|
if (view.childCount > 1) {
|
||||||
|
error("view has ${view.childCount} children, it should have maximum 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The static content for no conversations is already shown.
|
||||||
|
if (view.findViewById<View>(R.id.top_level_no_conversations) != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were showing the content with conversations earlier, remove it.
|
||||||
|
if (view.childCount == 1) {
|
||||||
|
view.removeViewAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = view.context
|
||||||
|
val noConversationsView =
|
||||||
|
LayoutInflater.from(context)
|
||||||
|
.inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
|
||||||
|
|
||||||
|
// The Tile preview has colorBackground as its background. Change it so it's different than
|
||||||
|
// the activity's background.
|
||||||
|
val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
|
||||||
|
val shape = item.background as GradientDrawable
|
||||||
|
val ta =
|
||||||
|
context.theme.obtainStyledAttributes(
|
||||||
|
intArrayOf(com.android.internal.R.attr.colorSurface)
|
||||||
|
)
|
||||||
|
shape.setColor(ta.getColor(0, Color.WHITE))
|
||||||
|
ta.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setConversationsContent(
|
||||||
|
view: ViewGroup,
|
||||||
|
priorityTiles: List<PeopleTileViewModel>,
|
||||||
|
recentTiles: List<PeopleTileViewModel>,
|
||||||
|
onTileClicked: (PeopleTileViewModel) -> Unit,
|
||||||
|
) {
|
||||||
|
// This should never happen.
|
||||||
|
if (view.childCount > 1) {
|
||||||
|
error("view has ${view.childCount} children, it should have maximum 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inflate the content with conversations, if it's not already.
|
||||||
|
if (view.findViewById<View>(R.id.top_level_with_conversations) == null) {
|
||||||
|
// If we were showing the content without conversations earlier, remove it.
|
||||||
|
if (view.childCount == 1) {
|
||||||
|
view.removeViewAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutInflater.from(view.context)
|
||||||
|
.inflate(R.layout.people_space_activity_with_conversations, /* root= */ view)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(b/193782241): Replace the NestedScrollView + 2x LinearLayout from this layout into a
|
||||||
|
// single RecyclerView once this screen is tested by screenshot tests. Introduce a
|
||||||
|
// PeopleSpaceTileViewBinder that will properly create and bind the View associated to a
|
||||||
|
// PeopleSpaceTileViewModel (and remove the PeopleSpaceTileView class).
|
||||||
|
val conversationsView = view.requireViewById<View>(R.id.top_level_with_conversations)
|
||||||
|
setTileViews(
|
||||||
|
conversationsView,
|
||||||
|
R.id.priority,
|
||||||
|
R.id.priority_tiles,
|
||||||
|
priorityTiles,
|
||||||
|
onTileClicked,
|
||||||
|
)
|
||||||
|
|
||||||
|
setTileViews(
|
||||||
|
conversationsView,
|
||||||
|
R.id.recent,
|
||||||
|
R.id.recent_tiles,
|
||||||
|
recentTiles,
|
||||||
|
onTileClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets a [PeopleSpaceTileView]s for each conversation. */
|
||||||
|
private fun setTileViews(
|
||||||
|
root: View,
|
||||||
|
tilesListId: Int,
|
||||||
|
tilesId: Int,
|
||||||
|
tiles: List<PeopleTileViewModel>,
|
||||||
|
onTileClicked: (PeopleTileViewModel) -> Unit,
|
||||||
|
) {
|
||||||
|
// Remove any previously added tile.
|
||||||
|
// TODO(b/193782241): Once this list is a big RecyclerView, set the current list and use
|
||||||
|
// DiffUtil to do as less addView/removeView as possible.
|
||||||
|
val layout = root.requireViewById<ViewGroup>(tilesId)
|
||||||
|
layout.removeAllViews()
|
||||||
|
layout.outlineProvider = ViewOutlineProvider
|
||||||
|
|
||||||
|
val tilesListView = root.requireViewById<LinearLayout>(tilesListId)
|
||||||
|
if (tiles.isEmpty()) {
|
||||||
|
tilesListView.visibility = View.GONE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tilesListView.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
// Add each tile.
|
||||||
|
tiles.forEachIndexed { i, tile ->
|
||||||
|
val tileView =
|
||||||
|
PeopleSpaceTileView(root.context, layout, tile.key.shortcutId, i == tiles.size - 1)
|
||||||
|
bindTileView(tileView, tile, onTileClicked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets [tileView] with the data in [conversation]. */
|
||||||
|
private fun bindTileView(
|
||||||
|
tileView: PeopleSpaceTileView,
|
||||||
|
tile: PeopleTileViewModel,
|
||||||
|
onTileClicked: (PeopleTileViewModel) -> Unit,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
tileView.setName(tile.username)
|
||||||
|
tileView.setPersonIcon(tile.icon)
|
||||||
|
tileView.setOnClickListener { onTileClicked(tile) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Couldn't retrieve shortcut information", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.ui.viewmodel
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import com.android.systemui.people.widget.PeopleTileKey
|
||||||
|
|
||||||
|
/** Models UI state for a single tile/conversation. */
|
||||||
|
data class PeopleTileViewModel(
|
||||||
|
val key: PeopleTileKey,
|
||||||
|
val icon: Bitmap,
|
||||||
|
val username: String?,
|
||||||
|
)
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.ui.viewmodel
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.people.PeopleSpaceUtils
|
||||||
|
import com.android.systemui.people.PeopleTileViewHelper
|
||||||
|
import com.android.systemui.people.data.model.PeopleTileModel
|
||||||
|
import com.android.systemui.people.data.repository.PeopleTileRepository
|
||||||
|
import com.android.systemui.people.data.repository.PeopleWidgetRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models UI state for the people space, allowing the user to select which conversation should be
|
||||||
|
* associated to a new or existing Conversation widget.
|
||||||
|
*/
|
||||||
|
class PeopleViewModel(
|
||||||
|
@Application private val context: Context,
|
||||||
|
private val tileRepository: PeopleTileRepository,
|
||||||
|
private val widgetRepository: PeopleWidgetRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
/**
|
||||||
|
* The list of the priority tiles/conversations.
|
||||||
|
*
|
||||||
|
* Important: Even though this is a Flow, the underlying API used to populate this Flow is not
|
||||||
|
* reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
|
||||||
|
*/
|
||||||
|
private val _priorityTiles = MutableStateFlow(priorityTiles())
|
||||||
|
val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of the priority tiles/conversations.
|
||||||
|
*
|
||||||
|
* Important: Even though this is a Flow, the underlying API used to populate this Flow is not
|
||||||
|
* reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
|
||||||
|
*/
|
||||||
|
private val _recentTiles = MutableStateFlow(recentTiles())
|
||||||
|
val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles
|
||||||
|
|
||||||
|
/** The ID of the widget currently being edited/added. */
|
||||||
|
private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
|
||||||
|
val appWidgetId: StateFlow<Int> = _appWidgetId
|
||||||
|
|
||||||
|
/** Whether the user journey is complete. */
|
||||||
|
private val _isFinished = MutableStateFlow(false)
|
||||||
|
val isFinished: StateFlow<Boolean> = _isFinished
|
||||||
|
|
||||||
|
/** Refresh the [priorityTiles] and [recentTiles]. */
|
||||||
|
fun onTileRefreshRequested() {
|
||||||
|
_priorityTiles.value = priorityTiles()
|
||||||
|
_recentTiles.value = recentTiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when the [appWidgetId] should be changed to [widgetId]. */
|
||||||
|
fun onWidgetIdChanged(widgetId: Int) {
|
||||||
|
_appWidgetId.value = widgetId
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear [isFinished], setting it to false. */
|
||||||
|
fun clearIsFinished() {
|
||||||
|
_isFinished.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when a tile is clicked. */
|
||||||
|
fun onTileClicked(tile: PeopleTileViewModel) {
|
||||||
|
if (PeopleSpaceUtils.DEBUG) {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID: " +
|
||||||
|
_appWidgetId.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
widgetRepository.setWidgetTile(_appWidgetId.value, tile.key)
|
||||||
|
_isFinished.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun priorityTiles(): List<PeopleTileViewModel> {
|
||||||
|
return try {
|
||||||
|
tileRepository.priorityTiles().map { it.toViewModel() }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Couldn't retrieve priority conversations", e)
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recentTiles(): List<PeopleTileViewModel> {
|
||||||
|
return try {
|
||||||
|
tileRepository.recentTiles().map { it.toViewModel() }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Couldn't retrieve recent conversations", e)
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PeopleTileModel.toViewModel(): PeopleTileViewModel {
|
||||||
|
val icon =
|
||||||
|
PeopleTileViewHelper.getPersonIconBitmap(
|
||||||
|
context,
|
||||||
|
this,
|
||||||
|
PeopleTileViewHelper.getSizeInDp(
|
||||||
|
context,
|
||||||
|
R.dimen.avatar_size_for_medium,
|
||||||
|
context.resources.displayMetrics.density,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return PeopleTileViewModel(key, icon, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The Factory that should be used to create a [PeopleViewModel]. */
|
||||||
|
class Factory
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
@Application private val context: Context,
|
||||||
|
private val tileRepository: PeopleTileRepository,
|
||||||
|
private val widgetRepository: PeopleWidgetRepository,
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
check(modelClass == PeopleViewModel::class.java)
|
||||||
|
return PeopleViewModel(context, tileRepository, widgetRepository) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "PeopleSpaceViewModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user