Remove DashboardSummary and all related classes.

- Condition is already supported in PersonalSettingsFragment
- Suggestion is already supported in PersonalSettingsFragment
- Static/dynamic tiles are supported in TopLevelSettings

Change-Id: I51882e3bd0919ad95109baefac683d98667c11e3
Fixes: 110405144
Test: robotests
This commit is contained in:
Fan Zhang
2018-10-01 12:14:06 -07:00
parent 61a8d1fbe4
commit 130c477044
24 changed files with 7 additions and 3561 deletions

View File

@@ -1,407 +0,0 @@
/*
* Copyright (C) 2015 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.settings.dashboard;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.R.id;
import com.android.settings.dashboard.DashboardData.ConditionHeaderData;
import com.android.settings.dashboard.suggestions.SuggestionAdapter;
import com.android.settings.homepage.conditional.ConditionAdapter;
import com.android.settings.homepage.conditional.ConditionManager;
import com.android.settings.homepage.conditional.ConditionalCard;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.RoundedHomepageIcon;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.suggestions.SuggestionControllerMixinCompat;
import com.android.settingslib.utils.IconCache;
import java.util.List;
public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
implements SummaryLoader.SummaryConsumer, SuggestionAdapter.Callback, LifecycleObserver,
OnSaveInstanceState {
public static final String TAG = "DashboardAdapter";
private static final String STATE_CATEGORY_LIST = "category_list";
@VisibleForTesting
static final String STATE_CONDITION_EXPANDED = "condition_expanded";
private final IconCache mCache;
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final DashboardFeatureProvider mDashboardFeatureProvider;
private boolean mFirstFrameDrawn;
private RecyclerView mRecyclerView;
private SuggestionAdapter mSuggestionAdapter;
private ConditionManager mConditionManager;
@VisibleForTesting
DashboardData mDashboardData;
private View.OnClickListener mTileClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
//TODO: get rid of setTag/getTag
mDashboardFeatureProvider.openTileIntent((FragmentActivity) mContext,
(Tile) v.getTag());
}
};
public DashboardAdapter(Context context, Bundle savedInstanceState,
ConditionManager conditionManager,
SuggestionControllerMixinCompat suggestionControllerMixin, Lifecycle lifecycle) {
DashboardCategory category = null;
boolean conditionExpanded = false;
mContext = context;
final FeatureFactory factory = FeatureFactory.getFactory(context);
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);
mCache = new IconCache(context);
mConditionManager = conditionManager;
mSuggestionAdapter = new SuggestionAdapter(mContext, suggestionControllerMixin,
savedInstanceState, this /* callback */, lifecycle);
setHasStableIds(true);
if (savedInstanceState != null) {
category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);
conditionExpanded = savedInstanceState.getBoolean(
STATE_CONDITION_EXPANDED, conditionExpanded);
}
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mDashboardData = new DashboardData.Builder()
.setSuggestions(mSuggestionAdapter.getSuggestions())
.setCategory(category)
.setConditionExpanded(conditionExpanded)
.build();
}
public void setSuggestions(List<Suggestion> data) {
final DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData)
.setSuggestions(data)
.build();
notifyDashboardDataChanged(prevData);
}
public void setCategory(DashboardCategory category) {
final DashboardData prevData = mDashboardData;
Log.d(TAG, "adapter setCategory called");
mDashboardData = new DashboardData.Builder(prevData)
.setCategory(category)
.build();
notifyDashboardDataChanged(prevData);
}
public void setConditions(List<ConditionalCard> conditions) {
final DashboardData prevData = mDashboardData;
Log.d(TAG, "adapter setConditions called");
mDashboardData = new DashboardData.Builder(prevData)
.setConditions(conditions)
.build();
notifyDashboardDataChanged(prevData);
}
@Override
public void onSuggestionClosed(Suggestion suggestion) {
final List<Suggestion> list = mDashboardData.getSuggestions();
if (list == null || list.size() == 0 || !list.remove(suggestion)) {
return;
}
if (list.isEmpty()) {
// The only suggestion is dismissed, and the the empty suggestion container will
// remain as the dashboard item. Need to refresh the dashboard list.
setSuggestions(null);
} else {
setSuggestions(list);
}
}
@Override
public void notifySummaryChanged(Tile tile) {
final int position = mDashboardData.getPositionByTile(tile);
if (position != DashboardData.POSITION_NOT_FOUND) {
// Since usually tile in parameter and tile in mCategories are same instance,
// which is hard to be detected by DiffUtil, so we notifyItemChanged directly.
notifyItemChanged(position, mDashboardData.getItemTypeByPosition(position));
}
}
@Override
public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
if (viewType == R.layout.condition_header) {
return new ConditionHeaderHolder(view);
}
if (viewType == R.layout.condition_container) {
return new ConditionContainerHolder(view);
}
if (viewType == R.layout.suggestion_container) {
return new SuggestionContainerHolder(view);
}
return new DashboardItemHolder(view);
}
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
final int type = mDashboardData.getItemTypeByPosition(position);
switch (type) {
case R.layout.dashboard_tile:
final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
onBindTile(holder, tile);
holder.itemView.setTag(tile);
holder.itemView.setOnClickListener(mTileClickListener);
break;
case R.layout.suggestion_container:
onBindSuggestion((SuggestionContainerHolder) holder, position);
break;
case R.layout.condition_container:
onBindCondition((ConditionContainerHolder) holder, position);
break;
case R.layout.condition_header:
onBindConditionHeader((ConditionHeaderHolder) holder,
(ConditionHeaderData) mDashboardData.getItemEntityByPosition(position));
break;
case R.layout.condition_footer:
holder.itemView.setOnClickListener(v -> {
mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData).
setConditionExpanded(false).build();
notifyDashboardDataChanged(prevData);
scrollToTopOfConditions();
});
break;
}
}
@Override
public long getItemId(int position) {
return mDashboardData.getItemIdByPosition(position);
}
@Override
public int getItemViewType(int position) {
return mDashboardData.getItemTypeByPosition(position);
}
@Override
public int getItemCount() {
return mDashboardData.size();
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
// save the view so that we can scroll it when expanding/collapsing the suggestion and
// conditions.
mRecyclerView = recyclerView;
}
public Object getItem(long itemId) {
return mDashboardData.getItemEntityById(itemId);
}
@VisibleForTesting
void notifyDashboardDataChanged(DashboardData prevData) {
if (mFirstFrameDrawn && prevData != null) {
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DashboardData
.ItemsDataDiffCallback(prevData.getItemList(), mDashboardData.getItemList()));
diffResult.dispatchUpdatesTo(this);
} else {
mFirstFrameDrawn = true;
notifyDataSetChanged();
}
}
@VisibleForTesting
void onBindConditionHeader(final ConditionHeaderHolder holder, ConditionHeaderData data) {
holder.icon.setImageDrawable(data.conditionIcons.get(0));
if (data.conditionCount == 1) {
holder.title.setText(data.title);
holder.summary.setText(null);
holder.icons.setVisibility(View.INVISIBLE);
} else {
holder.title.setText(null);
holder.summary.setText(
mContext.getString(R.string.condition_summary, data.conditionCount));
updateConditionIcons(data.conditionIcons, holder.icons);
holder.icons.setVisibility(View.VISIBLE);
}
holder.itemView.setOnClickListener(v -> {
mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
final DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData)
.setConditionExpanded(true).build();
notifyDashboardDataChanged(prevData);
scrollToTopOfConditions();
});
}
@VisibleForTesting
void onBindCondition(final ConditionContainerHolder holder, int position) {
final List<ConditionalCard> conditions =
(List) mDashboardData.getItemEntityByPosition(position);
final ConditionAdapter adapter = new ConditionAdapter(
mContext, mConditionManager, conditions,
mDashboardData.isConditionExpanded());
holder.data.setAdapter(adapter);
holder.data.setLayoutManager(new LinearLayoutManager(mContext));
}
@VisibleForTesting
void onBindSuggestion(final SuggestionContainerHolder holder, int position) {
// If there is suggestions to show, it will be at position 0 as we don't show the suggestion
// header anymore.
final List<Suggestion> suggestions =
(List<Suggestion>) mDashboardData.getItemEntityByPosition(position);
if (suggestions != null && suggestions.size() > 0) {
mSuggestionAdapter.setSuggestions(suggestions);
holder.data.setAdapter(mSuggestionAdapter);
}
final LinearLayoutManager layoutManager = new LinearLayoutManager(mContext);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
holder.data.setLayoutManager(layoutManager);
}
@VisibleForTesting
void onBindTile(DashboardItemHolder holder, Tile tile) {
Icon tileIcon = tile.getIcon(mContext);
Drawable icon = mCache.getIcon(tileIcon);
if (!TextUtils.equals(tileIcon.getResPackage(), mContext.getPackageName())
&& !(icon instanceof RoundedHomepageIcon)) {
icon = new RoundedHomepageIcon(mContext, icon);
((RoundedHomepageIcon) icon).setBackgroundColor(mContext, tile);
mCache.updateIcon(tileIcon, icon);
}
holder.icon.setImageDrawable(icon);
holder.title.setText(tile.getTitle(mContext));
final CharSequence summary = tile.getSummary(mContext);
if (!TextUtils.isEmpty(summary)) {
holder.summary.setText(summary);
holder.summary.setVisibility(View.VISIBLE);
} else {
holder.summary.setVisibility(View.GONE);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
final DashboardCategory category = mDashboardData.getCategory();
if (category != null) {
outState.putParcelable(STATE_CATEGORY_LIST, category);
}
outState.putBoolean(STATE_CONDITION_EXPANDED, mDashboardData.isConditionExpanded());
}
private void updateConditionIcons(List<Drawable> icons, ViewGroup parent) {
if (icons == null || icons.size() < 2) {
parent.setVisibility(View.INVISIBLE);
return;
}
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
parent.removeAllViews();
for (int i = 1, size = icons.size(); i < size; i++) {
ImageView icon = (ImageView) inflater.inflate(
R.layout.condition_header_icon, parent, false);
icon.setImageDrawable(icons.get(i));
parent.addView(icon);
}
parent.setVisibility(View.VISIBLE);
}
private void scrollToTopOfConditions() {
mRecyclerView.scrollToPosition(mDashboardData.hasSuggestion() ? 1 : 0);
}
public static class DashboardItemHolder extends RecyclerView.ViewHolder {
public final ImageView icon;
public final TextView title;
public final TextView summary;
public DashboardItemHolder(View itemView) {
super(itemView);
icon = itemView.findViewById(android.R.id.icon);
title = itemView.findViewById(android.R.id.title);
summary = itemView.findViewById(android.R.id.summary);
}
}
public static class ConditionHeaderHolder extends DashboardItemHolder {
public final LinearLayout icons;
public final ImageView expandIndicator;
public ConditionHeaderHolder(View itemView) {
super(itemView);
icons = itemView.findViewById(id.additional_icons);
expandIndicator = itemView.findViewById(id.expand_indicator);
}
}
public static class ConditionContainerHolder extends DashboardItemHolder {
public final RecyclerView data;
public ConditionContainerHolder(View itemView) {
super(itemView);
data = itemView.findViewById(id.data);
}
}
public static class SuggestionContainerHolder extends DashboardItemHolder {
public final RecyclerView data;
public SuggestionContainerHolder(View itemView) {
super(itemView);
data = itemView.findViewById(id.suggestion_list);
}
}
}

View File

@@ -1,416 +0,0 @@
/*
* Copyright (C) 2016 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.settings.dashboard;
import android.annotation.IntDef;
import android.graphics.drawable.Drawable;
import android.service.settings.suggestions.Suggestion;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.DiffUtil;
import com.android.settings.R;
import com.android.settings.homepage.conditional.ConditionalCard;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* Description about data list used in the DashboardAdapter. In the data list each item can be
* Condition, suggestion or category tile.
* <p>
* ItemsData has inner class Item, which represents the Item in data list.
*/
public class DashboardData {
public static final int POSITION_NOT_FOUND = -1;
// stable id for different type of items.
@VisibleForTesting
static final int STABLE_ID_SUGGESTION_CONTAINER = 0;
static final int STABLE_ID_SUGGESTION_CONDITION_DIVIDER = 1;
@VisibleForTesting
static final int STABLE_ID_CONDITION_HEADER = 2;
@VisibleForTesting
static final int STABLE_ID_CONDITION_FOOTER = 3;
@VisibleForTesting
static final int STABLE_ID_CONDITION_CONTAINER = 4;
private final List<Item> mItems;
private final DashboardCategory mCategory;
private final List<ConditionalCard> mConditions;
private final List<Suggestion> mSuggestions;
private final boolean mConditionExpanded;
private DashboardData(Builder builder) {
mCategory = builder.mCategory;
mConditions = builder.mConditions;
mSuggestions = builder.mSuggestions;
mConditionExpanded = builder.mConditionExpanded;
mItems = new ArrayList<>();
buildItemsData();
}
public int getItemIdByPosition(int position) {
return mItems.get(position).id;
}
public int getItemTypeByPosition(int position) {
return mItems.get(position).type;
}
public Object getItemEntityByPosition(int position) {
return mItems.get(position).entity;
}
public List<Item> getItemList() {
return mItems;
}
public int size() {
return mItems.size();
}
public Object getItemEntityById(long id) {
for (final Item item : mItems) {
if (item.id == id) {
return item.entity;
}
}
return null;
}
public DashboardCategory getCategory() {
return mCategory;
}
public List<ConditionalCard> getConditions() {
return mConditions;
}
public List<Suggestion> getSuggestions() {
return mSuggestions;
}
public boolean hasSuggestion() {
return sizeOf(mSuggestions) > 0;
}
public boolean isConditionExpanded() {
return mConditionExpanded;
}
/**
* Find the position of the object in mItems list, using the equals method to compare
*
* @param entity the object that need to be found in list
* @return position of the object, return POSITION_NOT_FOUND if object isn't in the list
*/
public int getPositionByEntity(Object entity) {
if (entity == null) return POSITION_NOT_FOUND;
final int size = mItems.size();
for (int i = 0; i < size; i++) {
final Object item = mItems.get(i).entity;
if (entity.equals(item)) {
return i;
}
}
return POSITION_NOT_FOUND;
}
/**
* Find the position of the Tile object.
* <p>
* First, try to find the exact identical instance of the tile object, if not found,
* then try to find a tile has the same id.
*
* @param tile tile that need to be found
* @return position of the object, return INDEX_NOT_FOUND if object isn't in the list
*/
public int getPositionByTile(Tile tile) {
final int size = mItems.size();
for (int i = 0; i < size; i++) {
final Object entity = mItems.get(i).entity;
if (entity == tile) {
return i;
} else if (entity instanceof Tile && tile.getId() == ((Tile) entity).getId()) {
return i;
}
}
return POSITION_NOT_FOUND;
}
/**
* Add item into list when {@paramref add} is true.
*
* @param item maybe {@link ConditionalCard}, {@link Tile}, {@link DashboardCategory}
* or null
* @param type type of the item, and value is the layout id
* @param stableId The stable id for this item
* @param add flag about whether to add item into list
*/
private void addToItemList(Object item, int type, int stableId, boolean add) {
if (add) {
mItems.add(new Item(item, type, stableId));
}
}
/**
* Build the mItems list using mConditions, mSuggestions, mCategories data
* and mIsShowingAll, mConditionExpanded flag.
*/
private void buildItemsData() {
final List<ConditionalCard> conditions = mConditions;
final boolean hasConditions = sizeOf(conditions) > 0;
final List<Suggestion> suggestions = mSuggestions;
final boolean hasSuggestions = sizeOf(suggestions) > 0;
/* Suggestion container. This is the card view that contains the list of suggestions.
* This will be added whenever the suggestion list is not empty */
addToItemList(suggestions, R.layout.suggestion_container,
STABLE_ID_SUGGESTION_CONTAINER, hasSuggestions);
/* Divider between suggestion and conditions if both are present. */
addToItemList(null /* item */, R.layout.horizontal_divider,
STABLE_ID_SUGGESTION_CONDITION_DIVIDER, hasSuggestions && hasConditions);
/* Condition header. This will be present when there is condition and it is collapsed */
addToItemList(new ConditionHeaderData(conditions),
R.layout.condition_header,
STABLE_ID_CONDITION_HEADER, hasConditions && !mConditionExpanded);
/* Condition container. This is the card view that contains the list of conditions.
* This will be added whenever the condition list is not empty and expanded */
addToItemList(conditions, R.layout.condition_container,
STABLE_ID_CONDITION_CONTAINER, hasConditions && mConditionExpanded);
/* Condition footer. This will be present when there is condition and it is expanded */
addToItemList(null /* item */, R.layout.condition_footer,
STABLE_ID_CONDITION_FOOTER, hasConditions && mConditionExpanded);
if (mCategory != null) {
final List<Tile> tiles = mCategory.getTiles();
for (int i = 0; i < tiles.size(); i++) {
final Tile tile = tiles.get(i);
addToItemList(tile, R.layout.dashboard_tile, tile.getId(),
true /* add */);
}
}
}
private static int sizeOf(List<?> list) {
return list == null ? 0 : list.size();
}
/**
* Builder used to build the ItemsData
*/
public static class Builder {
private DashboardCategory mCategory;
private List<ConditionalCard> mConditions;
private List<Suggestion> mSuggestions;
private boolean mConditionExpanded;
public Builder() {
}
public Builder(DashboardData dashboardData) {
mCategory = dashboardData.mCategory;
mConditions = dashboardData.mConditions;
mSuggestions = dashboardData.mSuggestions;
mConditionExpanded = dashboardData.mConditionExpanded;
}
public Builder setCategory(DashboardCategory category) {
this.mCategory = category;
return this;
}
public Builder setConditions(List<ConditionalCard> conditions) {
this.mConditions = conditions;
return this;
}
public Builder setSuggestions(List<Suggestion> suggestions) {
this.mSuggestions = suggestions;
return this;
}
public Builder setConditionExpanded(boolean expanded) {
this.mConditionExpanded = expanded;
return this;
}
public DashboardData build() {
return new DashboardData(this);
}
}
/**
* A DiffCallback to calculate the difference between old and new Item
* List in DashboardData
*/
public static class ItemsDataDiffCallback extends DiffUtil.Callback {
final private List<Item> mOldItems;
final private List<Item> mNewItems;
public ItemsDataDiffCallback(List<Item> oldItems, List<Item> newItems) {
mOldItems = oldItems;
mNewItems = newItems;
}
@Override
public int getOldListSize() {
return mOldItems.size();
}
@Override
public int getNewListSize() {
return mNewItems.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldItems.get(oldItemPosition).id == mNewItems.get(newItemPosition).id;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return mOldItems.get(oldItemPosition).equals(mNewItems.get(newItemPosition));
}
}
/**
* An item contains the data needed in the DashboardData.
*/
static class Item {
// valid types in field type
private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;
private static final int TYPE_SUGGESTION_CONTAINER =
R.layout.suggestion_container;
private static final int TYPE_CONDITION_CONTAINER =
R.layout.condition_container;
private static final int TYPE_CONDITION_HEADER =
R.layout.condition_header;
private static final int TYPE_CONDITION_FOOTER =
R.layout.condition_footer;
private static final int TYPE_SUGGESTION_CONDITION_DIVIDER = R.layout.horizontal_divider;
@IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONTAINER, TYPE_CONDITION_CONTAINER,
TYPE_CONDITION_HEADER, TYPE_CONDITION_FOOTER, TYPE_SUGGESTION_CONDITION_DIVIDER})
@Retention(RetentionPolicy.SOURCE)
public @interface ItemTypes {
}
/**
* The main data object in item, usually is a {@link Tile}, {@link ConditionalCard}
* object. This object can also be null when the
* item is an divider line. Please refer to {@link #buildItemsData()} for
* detail usage of the Item.
*/
public final Object entity;
/**
* The type of item, value inside is the layout id(e.g. R.layout.dashboard_tile)
*/
@ItemTypes
public final int type;
/**
* Id of this item, used in the {@link ItemsDataDiffCallback} to identify the same item.
*/
public final int id;
public Item(Object entity, @ItemTypes int type, int id) {
this.entity = entity;
this.type = type;
this.id = id;
}
/**
* Override it to make comparision in the {@link ItemsDataDiffCallback}
*
* @param obj object to compared with
* @return true if the same object or has equal value.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Item)) {
return false;
}
final Item targetItem = (Item) obj;
if (type != targetItem.type || id != targetItem.id) {
return false;
}
switch (type) {
case TYPE_DASHBOARD_TILE:
final Tile localTile = (Tile) entity;
final Tile targetTile = (Tile) targetItem.entity;
// Only check id and summary for dashboard tile
return localTile.getId() == targetTile.getId()
&& TextUtils.equals(
localTile.getSummaryReference(),
targetTile.getSummaryReference());
case TYPE_SUGGESTION_CONTAINER:
case TYPE_CONDITION_CONTAINER:
// Fall through to default
default:
return entity == null ? targetItem.entity == null
: entity.equals(targetItem.entity);
}
}
}
/**
* This class contains the data needed to build the suggestion/condition header. The data can
* also be used to check the diff in DiffUtil.Callback
*/
public static class ConditionHeaderData {
public final List<Drawable> conditionIcons;
public final CharSequence title;
public final int conditionCount;
public ConditionHeaderData(List<ConditionalCard> conditions) {
conditionCount = sizeOf(conditions);
title = conditionCount > 0 ? conditions.get(0).getTitle() : null;
conditionIcons = new ArrayList<>();
if (conditions == null) {
return;
}
for (ConditionalCard card : conditions) {
conditionIcons.add(card.getIcon());
}
}
}
}

View File

@@ -30,6 +30,7 @@ import com.android.settings.deviceinfo.StorageDashboardFragment;
import com.android.settings.display.NightDisplaySettings;
import com.android.settings.fuelgauge.PowerUsageSummary;
import com.android.settings.gestures.GestureSettings;
import com.android.settings.homepage.TopLevelSettings;
import com.android.settings.language.LanguageAndInputSettings;
import com.android.settings.network.NetworkDashboardFragment;
import com.android.settings.notification.ConfigureNotificationSettings;
@@ -61,9 +62,8 @@ public class DashboardFragmentRegistry {
static {
PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>();
// TODO(b/110405144): Add the mapping when IA.homepage intent-filter is is removed.
// PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(),
// CategoryKey.CATEGORY_HOMEPAGE);
PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(),
CategoryKey.CATEGORY_HOMEPAGE);
PARENT_TO_CATEGORY_KEY_MAP.put(
NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK);
PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(),

View File

@@ -1,45 +0,0 @@
/*
* Copyright (C) 2016 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.settings.dashboard;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.settingslib.drawer.Tile;
public class DashboardItemAnimator extends DefaultItemAnimator {
@Override
public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY,
int toX, int toY) {
final Object tag = oldHolder.itemView.getTag();
if (tag instanceof Tile && oldHolder == newHolder) {
// When this view has other move animation running, skip this value to avoid
// animations interrupt each other.
if (!isRunning()) {
fromX += ViewCompat.getTranslationX(oldHolder.itemView);
fromY += ViewCompat.getTranslationY(oldHolder.itemView);
}
if (fromX == toX && fromY == toY) {
dispatchMoveFinished(oldHolder);
return false;
}
}
return super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY);
}
}

View File

@@ -1,292 +0,0 @@
/*
* Copyright (C) 2014 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.settings.dashboard;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.service.settings.suggestions.Suggestion;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.loader.app.LoaderManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.core.SettingsBaseActivity.CategoryListener;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.homepage.conditional.ConditionListener;
import com.android.settings.homepage.conditional.ConditionManager;
import com.android.settings.homepage.conditional.FocusRecyclerView;
import com.android.settings.homepage.conditional.FocusRecyclerView.FocusListener;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.ActionBarShadowController;
import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.suggestions.SuggestionControllerMixinCompat;
import com.android.settingslib.utils.ThreadUtils;
import java.util.List;
/**
* Deprecated in favor of {@link com.android.settings.homepage.TopLevelSettings}
*
* @deprecated
*/
@Deprecated
public class DashboardSummary extends InstrumentedFragment
implements CategoryListener, ConditionListener,
FocusListener, SuggestionControllerMixinCompat.SuggestionControllerHost {
public static final boolean DEBUG = false;
private static final boolean DEBUG_TIMING = false;
private static final int MAX_WAIT_MILLIS = 3000;
private static final String TAG = "DashboardSummary";
private static final String STATE_SCROLL_POSITION = "scroll_position";
private static final String STATE_CATEGORIES_CHANGE_CALLED = "categories_change_called";
private final Handler mHandler = new Handler();
private FocusRecyclerView mDashboard;
private DashboardAdapter mAdapter;
private SummaryLoader mSummaryLoader;
private ConditionManager mConditionManager;
private LinearLayoutManager mLayoutManager;
private SuggestionControllerMixinCompat mSuggestionControllerMixin;
private DashboardFeatureProvider mDashboardFeatureProvider;
@VisibleForTesting
boolean mIsOnCategoriesChangedCalled;
private boolean mOnConditionsChangedCalled;
private DashboardCategory mStagingCategory;
private List<Suggestion> mStagingSuggestions;
@Override
public int getMetricsCategory() {
return MetricsEvent.DASHBOARD_SUMMARY;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d(TAG, "Creating SuggestionControllerMixinCompat");
final SuggestionFeatureProvider suggestionFeatureProvider = FeatureFactory
.getFactory(context)
.getSuggestionFeatureProvider(context);
if (suggestionFeatureProvider.isSuggestionEnabled(context)) {
mSuggestionControllerMixin = new SuggestionControllerMixinCompat(
context, this /* host */, getSettingsLifecycle(),
suggestionFeatureProvider.getSuggestionServiceComponent());
}
}
@Override
public LoaderManager getLoaderManager() {
if (!isAdded()) {
return null;
}
return super.getLoaderManager();
}
@Override
public void onCreate(Bundle savedInstanceState) {
long startTime = System.currentTimeMillis();
super.onCreate(savedInstanceState);
Log.d(TAG, "Starting DashboardSummary");
final Activity activity = getActivity();
mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
.getDashboardFeatureProvider(activity);
mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE);
mConditionManager =
new ConditionManager(
activity, this /* listener */);
if (savedInstanceState != null) {
mIsOnCategoriesChangedCalled =
savedInstanceState.getBoolean(STATE_CATEGORIES_CHANGE_CALLED);
}
if (DEBUG_TIMING) {
Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms");
}
}
@Override
public void onDestroy() {
mSummaryLoader.release();
super.onDestroy();
}
@Override
public void onResume() {
long startTime = System.currentTimeMillis();
super.onResume();
((SettingsBaseActivity) getActivity()).addCategoryListener(this);
mSummaryLoader.setListening(true);
mConditionManager.startMonitoringStateChange();
if (DEBUG_TIMING) {
Log.d(TAG, "onResume took " + (System.currentTimeMillis() - startTime) + " ms");
}
}
@Override
public void onPause() {
super.onPause();
((SettingsBaseActivity) getActivity()).remCategoryListener(this);
mSummaryLoader.setListening(false);
mConditionManager.stopMonitoringStateChange();
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
long startTime = System.currentTimeMillis();
if (hasWindowFocus) {
mConditionManager.startMonitoringStateChange();
} else {
mConditionManager.stopMonitoringStateChange();
}
if (DEBUG_TIMING) {
Log.d(TAG, "onWindowFocusChanged took "
+ (System.currentTimeMillis() - startTime) + " ms");
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mLayoutManager == null) {
return;
}
outState.putBoolean(STATE_CATEGORIES_CHANGE_CALLED, mIsOnCategoriesChangedCalled);
outState.putInt(STATE_SCROLL_POSITION, mLayoutManager.findFirstVisibleItemPosition());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
long startTime = System.currentTimeMillis();
final View root = inflater.inflate(R.layout.dashboard, container, false);
mDashboard = root.findViewById(R.id.dashboard_container);
mLayoutManager = new LinearLayoutManager(getContext());
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
if (bundle != null) {
int scrollPosition = bundle.getInt(STATE_SCROLL_POSITION);
mLayoutManager.scrollToPosition(scrollPosition);
}
mDashboard.setLayoutManager(mLayoutManager);
mDashboard.setHasFixedSize(true);
mDashboard.setListener(this);
mDashboard.setItemAnimator(new DashboardItemAnimator());
mAdapter = new DashboardAdapter(getContext(), bundle,
mConditionManager,
mSuggestionControllerMixin,
getSettingsLifecycle());
mDashboard.setAdapter(mAdapter);
mSummaryLoader.setSummaryConsumer(mAdapter);
ActionBarShadowController.attachToRecyclerView(
getActivity().findViewById(R.id.search_bar_container), getSettingsLifecycle(),
mDashboard);
rebuildUI();
if (DEBUG_TIMING) {
Log.d(TAG, "onCreateView took "
+ (System.currentTimeMillis() - startTime) + " ms");
}
return root;
}
@VisibleForTesting
void rebuildUI() {
ThreadUtils.postOnBackgroundThread(() -> updateCategory());
}
@Override
public void onCategoriesChanged() {
// Bypass rebuildUI() on the first call of onCategoriesChanged, since rebuildUI() happens
// in onViewCreated as well when app starts. But, on the subsequent calls we need to
// rebuildUI() because there might be some changes to suggestions and categories.
if (mIsOnCategoriesChangedCalled) {
rebuildUI();
}
mIsOnCategoriesChangedCalled = true;
}
@Override
public void onConditionsChanged() {
Log.d(TAG, "onConditionsChanged");
// Bypass refreshing the conditions on the first call of onConditionsChanged.
// onConditionsChanged is called immediately everytime we start listening to the conditions
// change when we gain window focus. Since the conditions are passed to the adapter's
// constructor when we create the view, the first handling is not necessary.
// But, on the subsequent calls we need to handle it because there might be real changes to
// conditions.
if (mOnConditionsChangedCalled) {
final boolean scrollToTop =
mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1;
mAdapter.setConditions(mConditionManager.getDisplayableCards());
if (scrollToTop) {
mDashboard.scrollToPosition(0);
}
} else {
mOnConditionsChangedCalled = true;
}
}
@Override
public void onSuggestionReady(List<Suggestion> suggestions) {
mStagingSuggestions = suggestions;
mAdapter.setSuggestions(suggestions);
if (mStagingCategory != null) {
Log.d(TAG, "Category has loaded, setting category from suggestionReady");
mHandler.removeCallbacksAndMessages(null);
mAdapter.setCategory(mStagingCategory);
}
}
@WorkerThread
void updateCategory() {
final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
CategoryKey.CATEGORY_HOMEPAGE);
mSummaryLoader.updateSummaryToCache(category);
mStagingCategory = category;
if (mSuggestionControllerMixin == null) {
ThreadUtils.postOnMainThread(() -> mAdapter.setCategory(mStagingCategory));
return;
}
if (mSuggestionControllerMixin.isSuggestionLoaded()) {
Log.d(TAG, "Suggestion has loaded, setting suggestion/category");
ThreadUtils.postOnMainThread(() -> {
if (mStagingSuggestions != null) {
mAdapter.setSuggestions(mStagingSuggestions);
}
mAdapter.setCategory(mStagingCategory);
});
} else {
Log.d(TAG, "Suggestion NOT loaded, delaying setCategory by " + MAX_WAIT_MILLIS + "ms");
mHandler.postDelayed(() -> mAdapter.setCategory(mStagingCategory), MAX_WAIT_MILLIS);
}
}
}

View File

@@ -44,7 +44,7 @@ import java.lang.reflect.Field;
import java.util.List;
public class SummaryLoader {
private static final boolean DEBUG = DashboardSummary.DEBUG;
private static final boolean DEBUG = false;
private static final String TAG = "SummaryLoader";
public static final String SUMMARY_PROVIDER_FACTORY = "SUMMARY_PROVIDER_FACTORY";

View File

@@ -1,277 +0,0 @@
/*
* Copyright (C) 2017 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.settings.dashboard.suggestions;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardAdapter.DashboardItemHolder;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
import com.android.settingslib.suggestions.SuggestionControllerMixinCompat;
import com.android.settingslib.utils.IconCache;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class SuggestionAdapter extends RecyclerView.Adapter<DashboardItemHolder> implements
LifecycleObserver, OnSaveInstanceState {
public static final String TAG = "SuggestionAdapter";
private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
private static final String STATE_SUGGESTION_LIST = "suggestion_list";
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final IconCache mCache;
private final ArrayList<String> mSuggestionsShownLogged;
private final SuggestionFeatureProvider mSuggestionFeatureProvider;
private final SuggestionControllerMixinCompat mSuggestionControllerMixin;
private final Callback mCallback;
private final CardConfig mConfig;
private List<Suggestion> mSuggestions;
public interface Callback {
/**
* Called when the close button of the suggestion card is clicked.
*/
void onSuggestionClosed(Suggestion suggestion);
}
public SuggestionAdapter(Context context,
SuggestionControllerMixinCompat suggestionControllerMixin, Bundle savedInstanceState,
Callback callback, Lifecycle lifecycle) {
mContext = context;
mSuggestionControllerMixin = suggestionControllerMixin;
mCache = new IconCache(context);
final FeatureFactory factory = FeatureFactory.getFactory(context);
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
mCallback = callback;
if (savedInstanceState != null) {
mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
STATE_SUGGESTIONS_SHOWN_LOGGED);
} else {
mSuggestionsShownLogged = new ArrayList<>();
}
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mConfig = CardConfig.get(context);
setHasStableIds(true);
}
@Override
public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
viewType, parent, false));
}
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
final Suggestion suggestion = mSuggestions.get(position);
final String id = suggestion.getId();
final int suggestionCount = mSuggestions.size();
if (!mSuggestionsShownLogged.contains(id)) {
mMetricsFeatureProvider.action(
mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
mSuggestionsShownLogged.add(id);
}
final Icon icon = suggestion.getIcon();
final Drawable drawable = mCache.getIcon(icon);
if (drawable != null && (suggestion.getFlags() & Suggestion.FLAG_ICON_TINTABLE) != 0) {
drawable.setTintList(Utils.getColorAccent(mContext));
}
holder.icon.setImageDrawable(drawable);
holder.title.setText(suggestion.getTitle());
holder.title.setTypeface(Typeface.create(
mContext.getString(com.android.internal.R.string.config_headlineFontFamily),
Typeface.NORMAL));
if (suggestionCount == 1) {
final CharSequence summary = suggestion.getSummary();
if (!TextUtils.isEmpty(summary)) {
holder.summary.setText(summary);
holder.summary.setVisibility(View.VISIBLE);
} else {
holder.summary.setVisibility(View.GONE);
}
} else {
mConfig.setCardLayout(holder, position);
}
final View closeButton = holder.itemView.findViewById(R.id.close_button);
if (closeButton != null) {
closeButton.setOnClickListener(v -> {
mSuggestionFeatureProvider.dismissSuggestion(
mContext, mSuggestionControllerMixin, suggestion);
if (mCallback != null) {
mCallback.onSuggestionClosed(suggestion);
}
});
}
View clickHandler = holder.itemView;
// If a view with @android:id/primary is defined, use that as the click handler
// instead.
final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
if (primaryAction != null) {
clickHandler = primaryAction;
}
clickHandler.setOnClickListener(v -> {
mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
try {
suggestion.getPendingIntent().send();
mSuggestionControllerMixin.launchSuggestion(suggestion);
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
}
});
}
@Override
public long getItemId(int position) {
return Objects.hash(mSuggestions.get(position).getId());
}
@Override
public int getItemViewType(int position) {
final Suggestion suggestion = getSuggestion(position);
if ((suggestion.getFlags() & Suggestion.FLAG_HAS_BUTTON) != 0) {
return R.layout.suggestion_tile_with_button;
}
if (getItemCount() == 1) {
return R.layout.suggestion_tile;
}
return R.layout.suggestion_tile_two_cards;
}
@Override
public int getItemCount() {
return mSuggestions.size();
}
public Suggestion getSuggestion(int position) {
final long itemId = getItemId(position);
if (mSuggestions == null) {
return null;
}
for (Suggestion suggestion : mSuggestions) {
if (Objects.hash(suggestion.getId()) == itemId) {
return suggestion;
}
}
return null;
}
public void removeSuggestion(Suggestion suggestion) {
final int position = mSuggestions.indexOf(suggestion);
mSuggestions.remove(suggestion);
notifyItemRemoved(position);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mSuggestions != null) {
outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
new ArrayList<>(mSuggestions));
}
outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
}
public void setSuggestions(List<Suggestion> suggestions) {
mSuggestions = suggestions;
}
public List<Suggestion> getSuggestions() {
return mSuggestions;
}
@VisibleForTesting
static class CardConfig {
// Card start/end margin
private final int mMarginInner;
private final int mMarginOuter;
private final WindowManager mWindowManager;
private static CardConfig sConfig;
private CardConfig(Context context) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Resources res = context.getResources();
mMarginInner =
res.getDimensionPixelOffset(R.dimen.suggestion_card_inner_margin);
mMarginOuter =
res.getDimensionPixelOffset(R.dimen.suggestion_card_outer_margin);
}
public static CardConfig get(Context context) {
if (sConfig == null) {
sConfig = new CardConfig(context);
}
return sConfig;
}
@VisibleForTesting
void setCardLayout(DashboardItemHolder holder, int position) {
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
getWidthForTwoCrads(), LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMarginStart(position == 0 ? mMarginOuter : mMarginInner);
params.setMarginEnd(position != 0 ? mMarginOuter : 0);
holder.itemView.setLayoutParams(params);
}
private int getWidthForTwoCrads() {
return (getScreenWidth() - mMarginInner - mMarginOuter * 2) / 2;
}
@VisibleForTesting
int getScreenWidth() {
final DisplayMetrics metrics = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(metrics);
return metrics.widthPixels;
}
}
}