From 6cb08a3ce468add4ea15b7398ed28bf22a71f66c Mon Sep 17 00:00:00 2001 From: Emily Chuang Date: Wed, 25 Jul 2018 20:35:06 +0800 Subject: [PATCH] Create the main architecture for Settings Dynamic Homepage Create an architecture for the homepage to enable card adding and displaying. Bug: 111676964 Test: robotests Change-Id: I06efe3d16585060a8e99313586ae29bbde7fe3e8 --- res/layout/dashboard.xml | 3 +- .../settings/homepage/CardContentLoader.java | 62 ++++ .../homepage/ControllerRendererPool.java | 99 ++++++ .../settings/homepage/HomepageAdapter.java | 96 ++++++ .../settings/homepage/HomepageCard.java | 296 ++++++++++++++++++ .../homepage/HomepageCardController.java | 45 +++ .../homepage/HomepageCardLookupTable.java | 73 +++++ .../homepage/HomepageCardRenderer.java | 46 +++ .../homepage/HomepageCardUpdateListener.java | 31 ++ .../settings/homepage/HomepageFragment.java | 12 + .../settings/homepage/HomepageManager.java | 135 ++++++++ 11 files changed, 896 insertions(+), 2 deletions(-) create mode 100644 src/com/android/settings/homepage/CardContentLoader.java create mode 100644 src/com/android/settings/homepage/ControllerRendererPool.java create mode 100644 src/com/android/settings/homepage/HomepageAdapter.java create mode 100644 src/com/android/settings/homepage/HomepageCard.java create mode 100644 src/com/android/settings/homepage/HomepageCardController.java create mode 100644 src/com/android/settings/homepage/HomepageCardLookupTable.java create mode 100644 src/com/android/settings/homepage/HomepageCardRenderer.java create mode 100644 src/com/android/settings/homepage/HomepageCardUpdateListener.java create mode 100644 src/com/android/settings/homepage/HomepageManager.java diff --git a/res/layout/dashboard.xml b/res/layout/dashboard.xml index ccb50ae10ed..8031028d8c6 100644 --- a/res/layout/dashboard.xml +++ b/res/layout/dashboard.xml @@ -26,5 +26,4 @@ android:paddingEnd="@dimen/dashboard_padding_end" android:paddingTop="@dimen/dashboard_padding_top" android:paddingBottom="@dimen/dashboard_padding_bottom" - android:scrollbars="vertical"/> - + android:scrollbars="vertical"/> \ No newline at end of file diff --git a/src/com/android/settings/homepage/CardContentLoader.java b/src/com/android/settings/homepage/CardContentLoader.java new file mode 100644 index 00000000000..4e1e33ef891 --- /dev/null +++ b/src/com/android/settings/homepage/CardContentLoader.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.Context; + +import androidx.annotation.Nullable; + +import com.android.settingslib.utils.AsyncLoaderCompat; + +import java.util.List; + +//TODO(b/112521307): Implement this to make it work with the card database. +public class CardContentLoader { + + private static final String TAG = "CardContentLoader"; + + private CardContentLoaderListener mListener; + + public interface CardContentLoaderListener { + void onFinishCardLoading(List homepageCards); + } + + public CardContentLoader() { + } + + void setListener(CardContentLoaderListener listener) { + mListener = listener; + } + + private static class CardLoader extends AsyncLoaderCompat> { + + public CardLoader(Context context) { + super(context); + } + + @Override + protected void onDiscardResult(List result) { + + } + + @Nullable + @Override + public List loadInBackground() { + return null; + } + } +} diff --git a/src/com/android/settings/homepage/ControllerRendererPool.java b/src/com/android/settings/homepage/ControllerRendererPool.java new file mode 100644 index 00000000000..b2ac9ecde9f --- /dev/null +++ b/src/com/android/settings/homepage/ControllerRendererPool.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.Context; +import android.util.Log; + +import androidx.collection.ArraySet; + +import java.util.Set; + +/** + * This is a fragment scoped singleton holding a set of {@link HomepageCardController} and + * {@link HomepageCardRenderer}. + */ +public class ControllerRendererPool { + + private static final String TAG = "ControllerRendererPool"; + + private final Set mControllers; + private final Set mRenderers; + + public ControllerRendererPool() { + mControllers = new ArraySet<>(); + mRenderers = new ArraySet<>(); + } + + public T getController(Context context, + @HomepageCard.CardType int cardType) { + final Class clz = + HomepageCardLookupTable.getCardControllerClass(cardType); + for (HomepageCardController controller : mControllers) { + if (controller.getClass() == clz) { + Log.d(TAG, "Controller is already there."); + return (T) controller; + } + } + + final HomepageCardController controller = createCardController(context, clz); + if (controller != null) { + mControllers.add(controller); + } + return (T) controller; + } + + public Set getControllers() { + return mControllers; + } + + public HomepageCardRenderer getRenderer(Context context, @HomepageCard.CardType int cardType) { + final Class clz = + HomepageCardLookupTable.getCardRendererClasses(cardType); + for (HomepageCardRenderer renderer : mRenderers) { + if (renderer.getClass() == clz) { + Log.d(TAG, "Renderer is already there."); + return renderer; + } + } + + final HomepageCardRenderer renderer = createCardRenderer(context, clz); + if (renderer != null) { + mRenderers.add(renderer); + } + return renderer; + } + + private HomepageCardController createCardController(Context context, + Class clz) { + /* + if (ConditionHomepageCardController.class == clz) { + return new ConditionHomepageCardController(context); + } + */ + return null; + } + + private HomepageCardRenderer createCardRenderer(Context context, Class clz) { + //if (ConditionHomepageCardRenderer.class == clz) { + // return new ConditionHomepageCardRenderer(context, this /*controllerRendererPool*/); + //} + + return null; + } + +} diff --git a/src/com/android/settings/homepage/HomepageAdapter.java b/src/com/android/settings/homepage/HomepageAdapter.java new file mode 100644 index 00000000000..b44288337d4 --- /dev/null +++ b/src/com/android/settings/homepage/HomepageAdapter.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +public class HomepageAdapter extends RecyclerView.Adapter implements + HomepageCardUpdateListener { + + private static final String TAG = "HomepageAdapter"; + + private final Context mContext; + private final ControllerRendererPool mControllerRendererPool; + + private List mHomepageCards; + private RecyclerView mRecyclerView; + + public HomepageAdapter(Context context, HomepageManager manager) { + mContext = context; + mHomepageCards = new ArrayList<>(); + mControllerRendererPool = manager.getControllerRendererPool(); + setHasStableIds(true); + } + + @Override + public long getItemId(int position) { + return mHomepageCards.get(position).hashCode(); + } + + @Override + public int getItemViewType(int position) { + return mHomepageCards.get(position).getCardType(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int cardType) { + final HomepageCardRenderer renderer = mControllerRendererPool.getRenderer(mContext, cardType); + final int viewType = renderer.getViewType(); + final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); + + return renderer.createViewHolder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + final int cardType = mHomepageCards.get(position).getCardType(); + final HomepageCardRenderer renderer = mControllerRendererPool.getRenderer(mContext, cardType); + + renderer.bindView(holder, mHomepageCards.get(position)); + } + + @Override + public int getItemCount() { + return mHomepageCards.size(); + } + + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + mRecyclerView = recyclerView; + } + + @Override + public void onHomepageCardUpdated(int cardType, List homepageCards) { + //TODO(b/112245748): Should implement a DiffCallback so we can use notifyItemChanged() + // instead. + if (homepageCards == null) { + mHomepageCards.clear(); + } else { + mHomepageCards = homepageCards; + } + notifyDataSetChanged(); + } +} diff --git a/src/com/android/settings/homepage/HomepageCard.java b/src/com/android/settings/homepage/HomepageCard.java new file mode 100644 index 00000000000..1719f57869a --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCard.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.annotation.IntDef; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.text.TextUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Data class representing a {@link HomepageCard}. + */ +public class HomepageCard { + + /** + * Flags indicating the type of the HomepageCard. + */ + @IntDef({CardType.INVALID, CardType.SLICE, CardType.SUGGESTION, CardType.CONDITIONAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface CardType { + int INVALID = -1; + int SLICE = 1; + int SUGGESTION = 2; + int CONDITIONAL = 3; + } + + private final String mName; + @CardType + private final int mCardType; + private final double mScore; + private final String mSliceUri; + private final int mCategory; + private final String mLocalizedToLocale; + private final String mPackageName; + private final String mAppVersion; + private final String mTitleResName; + private final String mTitleText; + private final String mSummaryResName; + private final String mSummaryText; + private final String mIconResName; + private final int mIconResId; + private final String mCardAction; + private final long mExpireTimeMS; + private final Drawable mDrawable; + private final boolean mSupportHalfWidth; + + String getName() { + return mName; + } + + int getCardType() { + return mCardType; + } + + double getScore() { + return mScore; + } + + String getTextSliceUri() { + return mSliceUri; + } + + Uri getSliceUri() { + return Uri.parse(mSliceUri); + } + + int getCategory() { + return mCategory; + } + + String getLocalizedToLocale() { + return mLocalizedToLocale; + } + + String getPackageName() { + return mPackageName; + } + + String getAppVersion() { + return mAppVersion; + } + + String getTitleResName() { + return mTitleResName; + } + + String getTitleText() { + return mTitleText; + } + + String getSummaryResName() { + return mSummaryResName; + } + + String getSummaryText() { + return mSummaryText; + } + + String getIconResName() { + return mIconResName; + } + + int getIconResId() { + return mIconResId; + } + + String getCardAction() { + return mCardAction; + } + + long getExpireTimeMS() { + return mExpireTimeMS; + } + + Drawable getDrawable() { + return mDrawable; + } + + boolean getSupportHalfWidth() { + return mSupportHalfWidth; + } + + HomepageCard(Builder builder) { + mName = builder.mName; + mCardType = builder.mCardType; + mScore = builder.mScore; + mSliceUri = builder.mSliceUri; + mCategory = builder.mCategory; + mLocalizedToLocale = builder.mLocalizedToLocale; + mPackageName = builder.mPackageName; + mAppVersion = builder.mAppVersion; + mTitleResName = builder.mTitleResName; + mTitleText = builder.mTitleText; + mSummaryResName = builder.mSummaryResName; + mSummaryText = builder.mSummaryText; + mIconResName = builder.mIconResName; + mIconResId = builder.mIconResId; + mCardAction = builder.mCardAction; + mExpireTimeMS = builder.mExpireTimeMS; + mDrawable = builder.mDrawable; + mSupportHalfWidth = builder.mSupportHalfWidth; + } + + @Override + public int hashCode() { + return mName.hashCode(); + } + + /** + * Note that {@link #mName} is treated as a primary key for this class and determines equality. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof HomepageCard)) { + return false; + } + final HomepageCard that = (HomepageCard) obj; + + return TextUtils.equals(mName, that.mName); + } + + static class Builder { + private String mName; + private int mCardType; + private double mScore; + private String mSliceUri; + private int mCategory; + private String mLocalizedToLocale; + private String mPackageName; + private String mAppVersion; + private String mTitleResName; + private String mTitleText; + private String mSummaryResName; + private String mSummaryText; + private String mIconResName; + private int mIconResId; + private String mCardAction; + private long mExpireTimeMS; + private Drawable mDrawable; + private boolean mSupportHalfWidth; + + public Builder setName(String name) { + mName = name; + return this; + } + + public Builder setCardType(int cardType) { + mCardType = cardType; + return this; + } + + public Builder setScore(double score) { + mScore = score; + return this; + } + + public Builder setSliceUri(String sliceUri) { + mSliceUri = sliceUri; + return this; + } + + public Builder setCategory(int category) { + mCategory = category; + return this; + } + + public Builder setLocalizedToLocale(String localizedToLocale) { + mLocalizedToLocale = localizedToLocale; + return this; + } + + public Builder setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + public Builder setAppVersion(String appVersion) { + mAppVersion = appVersion; + return this; + } + + public Builder setTitleResName(String titleResName) { + mTitleResName = titleResName; + return this; + } + + public Builder setTitleText(String titleText) { + mTitleText = titleText; + return this; + } + + public Builder setSummaryResName(String summaryResName) { + mSummaryResName = summaryResName; + return this; + } + + public Builder setSummaryText(String summaryText) { + mSummaryText = summaryText; + return this; + } + + public Builder setIconResName(String iconResName) { + mIconResName = iconResName; + return this; + } + + public Builder setIconResId(int iconResId) { + mIconResId = iconResId; + return this; + } + + public Builder setCardAction(String cardAction) { + mCardAction = cardAction; + return this; + } + + public Builder setExpireTimeMS(long expireTimeMS) { + mExpireTimeMS = expireTimeMS; + return this; + } + + public Builder setDrawable(Drawable drawable) { + mDrawable = drawable; + return this; + } + + public Builder setSupportHalfWidth(boolean supportHalfWidth) { + mSupportHalfWidth = supportHalfWidth; + return this; + } + + public HomepageCard build() { + return new HomepageCard(this); + } + } +} diff --git a/src/com/android/settings/homepage/HomepageCardController.java b/src/com/android/settings/homepage/HomepageCardController.java new file mode 100644 index 00000000000..c35c3c80c61 --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCardController.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 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.homepage; + +import com.android.settingslib.core.lifecycle.Lifecycle; + +import java.util.List; + +//TODO(b/111821137): add test cases +/** + * Data controller for {@link HomepageCard}. + */ +public interface HomepageCardController { + + @HomepageCard.CardType + int getCardType(); + + /** + * When data is updated or changed, the new data should be passed to HomepageManager for list + * updating. + */ + void onDataUpdated(List cardList); + + void onPrimaryClick(HomepageCard card); + + void onActionClick(HomepageCard card); + + void setLifecycle(Lifecycle lifecycle); + + void setHomepageCardUpdateListener(HomepageCardUpdateListener listener); +} diff --git a/src/com/android/settings/homepage/HomepageCardLookupTable.java b/src/com/android/settings/homepage/HomepageCardLookupTable.java new file mode 100644 index 00000000000..9e941e9dd3f --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCardLookupTable.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018 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.homepage; + +import com.android.settings.homepage.HomepageCard.CardType; + +import java.util.Set; +import java.util.TreeSet; + +public class HomepageCardLookupTable { + + static class HomepageMapping implements Comparable { + @CardType + private final int mCardType; + private final Class mControllerClass; + private final Class mRendererClass; + + private HomepageMapping(@CardType int cardType, + Class controllerClass, + Class rendererClass) { + mCardType = cardType; + mControllerClass = controllerClass; + mRendererClass = rendererClass; + } + + @Override + public int compareTo(HomepageMapping other) { + return Integer.compare(this.mCardType, other.mCardType); + } + } + + private static final Set LOOKUP_TABLE = new TreeSet() { + { + //add(new HomepageMapping(CardType.CONDITIONAL, ConditionHomepageCardController.class, + // ConditionHomepageCardRenderer.class)); + } + }; + + public static Class getCardControllerClass( + @CardType int cardType) { + for (HomepageMapping mapping : LOOKUP_TABLE) { + if (mapping.mCardType == cardType) { + return mapping.mControllerClass; + } + } + return null; + } + + //TODO(b/112578070): Implement multi renderer cases. + public static Class getCardRendererClasses( + @CardType int cardType) { + for (HomepageMapping mapping : LOOKUP_TABLE) { + if (mapping.mCardType == cardType) { + return mapping.mRendererClass; + } + } + return null; + } +} diff --git a/src/com/android/settings/homepage/HomepageCardRenderer.java b/src/com/android/settings/homepage/HomepageCardRenderer.java new file mode 100644 index 00000000000..ffa54e36a69 --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCardRenderer.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +/** + * UI renderer for {@link HomepageCard}. + */ +public interface HomepageCardRenderer { + + /** + * The layout type of the controller. + */ + int getViewType(); + + /** + * When {@link HomepageAdapter} calls {@link HomepageAdapter#onCreateViewHolder(ViewGroup, + * int)}, this method will be called to retrieve the corresponding + * {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}. + */ + RecyclerView.ViewHolder createViewHolder(View view); + + /** + * When {@link HomepageAdapter} calls {@link HomepageAdapter#onBindViewHolder(RecyclerView + * .ViewHolder, int)}, this method will be called to bind data to the + * {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}. + */ + void bindView(RecyclerView.ViewHolder holder, HomepageCard card); +} \ No newline at end of file diff --git a/src/com/android/settings/homepage/HomepageCardUpdateListener.java b/src/com/android/settings/homepage/HomepageCardUpdateListener.java new file mode 100644 index 00000000000..a44ba2be6e3 --- /dev/null +++ b/src/com/android/settings/homepage/HomepageCardUpdateListener.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 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.homepage; + +import java.util.List; + +/** + * When {@link HomepageCardController} detects changes, it will notify the listeners registered. In + * our case, {@link HomepageManager} gets noticed. + * + * After the list of {@link HomepageCard} gets updated in{@link HomepageManager}, + * {@link HomepageManager} will notify the listeners registered, {@link HomepageAdapter} in this + * case. + */ +interface HomepageCardUpdateListener { + void onHomepageCardUpdated(int cardType, List updateList); +} \ No newline at end of file diff --git a/src/com/android/settings/homepage/HomepageFragment.java b/src/com/android/settings/homepage/HomepageFragment.java index dc6a91fe27d..402725ebc90 100644 --- a/src/com/android/settings/homepage/HomepageFragment.java +++ b/src/com/android/settings/homepage/HomepageFragment.java @@ -48,6 +48,7 @@ public class HomepageFragment extends InstrumentedFragment { private static final String SAVE_BOTTOM_FRAGMENT_LOADED = "bottom_fragment_loaded"; private RecyclerView mCardsContainer; + private HomepageAdapter mHomepageAdapter; private LinearLayoutManager mLayoutManager; private FloatingActionButton mSearchButton; @@ -55,6 +56,14 @@ public class HomepageFragment extends InstrumentedFragment { private View mBottomBar; private View mSearchBar; private boolean mBottomFragmentLoaded; + private HomepageManager mHomepageManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHomepageManager = new HomepageManager(getContext(), getSettingsLifecycle()); + mHomepageManager.startCardContentLoading(); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -65,6 +74,9 @@ public class HomepageFragment extends InstrumentedFragment { //TODO(b/111822407): May have to swap to GridLayoutManager mLayoutManager = new LinearLayoutManager(getActivity()); mCardsContainer.setLayoutManager(mLayoutManager); + mHomepageAdapter = new HomepageAdapter(getContext(), mHomepageManager); + mCardsContainer.setAdapter(mHomepageAdapter); + mHomepageManager.setListener(mHomepageAdapter); return rootView; } diff --git a/src/com/android/settings/homepage/HomepageManager.java b/src/com/android/settings/homepage/HomepageManager.java new file mode 100644 index 00000000000..cbd5841d2cf --- /dev/null +++ b/src/com/android/settings/homepage/HomepageManager.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.Context; +import android.widget.BaseAdapter; + +import com.android.settingslib.core.lifecycle.Lifecycle; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a centralized manager of multiple {@link HomepageCardController}. + * + * {@link HomepageManager} first loads data from {@link CardContentLoader} and gets back a list of + * {@link HomepageCard}. All subclasses of {@link HomepageCardController} are loaded here, which + * will then trigger the {@link HomepageCardController} to load its data and listen to + * corresponding changes. When every single {@link HomepageCardController} updates its data, the + * data will be passed here, then going through some sorting mechanisms. The + * {@link HomepageCardController} will end up building a list of {@link HomepageCard} for {@link + * HomepageAdapter} and {@link BaseAdapter#notifyDataSetChanged()} will be called to get the page + * refreshed. + */ +public class HomepageManager implements CardContentLoader.CardContentLoaderListener, + HomepageCardUpdateListener { + + private static final String TAG = "HomepageManager"; + //The list for Settings Custom Card + @HomepageCard.CardType + private static final int[] SETTINGS_CARDS = {HomepageCard.CardType.CONDITIONAL}; + + private final Context mContext; + private final ControllerRendererPool mControllerRendererPool; + private final Lifecycle mLifecycle; + + private List mHomepageCards; + private HomepageCardUpdateListener mListener; + + + public HomepageManager(Context context, Lifecycle lifecycle) { + mContext = context; + mLifecycle = lifecycle; + mHomepageCards = new ArrayList<>(); + mControllerRendererPool = new ControllerRendererPool(); + } + + void startCardContentLoading() { + final CardContentLoader cardContentLoader = new CardContentLoader(); + cardContentLoader.setListener(this); + } + + private void loadCardControllers() { + if (mHomepageCards != null) { + for (HomepageCard card : mHomepageCards) { + setupController(card.getCardType()); + } + } + + //for data provided by Settings + for (int cardType : SETTINGS_CARDS) { + setupController(cardType); + } + } + + private void setupController(int cardType) { + final HomepageCardController controller = mControllerRendererPool.getController(mContext, + cardType); + if (controller != null) { + controller.setHomepageCardUpdateListener(this); + controller.setLifecycle(mLifecycle); + } + } + + //TODO(b/111822376): implement sorting mechanism. + private void sortCards() { + //take mHomepageCards as the source and do the ranking based on the rule. + } + + @Override + public void onHomepageCardUpdated(int cardType, List updateList) { + //TODO(b/112245748): Should implement a DiffCallback. + //Keep the old list for comparison. + final List prevCards = mHomepageCards; + + //Remove the existing data that matches the certain cardType so as to insert the new data. + for (int i = mHomepageCards.size() - 1; i >= 0; i--) { + if (mHomepageCards.get(i).getCardType() == cardType) { + mHomepageCards.remove(i); + } + } + + //Append the new data + mHomepageCards.addAll(updateList); + + sortCards(); + + if (mListener != null) { + mListener.onHomepageCardUpdated(HomepageCard.CardType.INVALID, mHomepageCards); + } + } + + @Override + public void onFinishCardLoading(List homepageCards) { + mHomepageCards = homepageCards; + + //Force card sorting here in case CardControllers of custom view have nothing to update + // for the first launch. + sortCards(); + + loadCardControllers(); + } + + void setListener(HomepageCardUpdateListener listener) { + mListener = listener; + } + + public ControllerRendererPool getControllerRendererPool() { + return mControllerRendererPool; + } +}