1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settingslib.widget;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.text.TextUtils;
22 import android.util.Log;
23 import android.view.View;
24 import android.widget.Button;
25 import android.widget.ImageView;
26 import android.widget.TextView;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import androidx.annotation.StringRes;
31 import androidx.annotation.VisibleForTesting;
32 
33 /**
34  * This class is used to initialize view which was inflated
35  * from {@link R.xml.app_entities_header.xml}.
36  *
37  * <p>How to use AppEntitiesHeaderController?
38  *
39  * <p>1. Add a {@link LayoutPreference} in layout XML file.
40  * <pre>
41  * &lt;com.android.settingslib.widget.LayoutPreference
42  *        android:key="app_entities_header"
43  *        android:layout="@layout/app_entities_header"/&gt;
44  * </pre>
45  *
46  * <p>2. Use AppEntitiesHeaderController to call below methods, then you can initialize
47  * view of <code>app_entities_header</code>.
48  *
49  * <pre>
50  *
51  * View headerView = ((LayoutPreference) screen.findPreference("app_entities_header"))
52  *         .findViewById(R.id.app_entities_header);
53  *
54  * final AppEntityInfo appEntityInfo = new AppEntityInfo.Builder()
55  *         .setIcon(icon)
56  *         .setTitle(title)
57  *         .setSummary(summary)
58  *         .setOnClickListener(view -> doSomething())
59  *         .build();
60  *
61  * AppEntitiesHeaderController.newInstance(context, headerView)
62  *         .setHeaderTitleRes(R.string.xxxxx)
63  *         .setHeaderDetailsRes(R.string.xxxxx)
64  *         .setHeaderDetailsClickListener(onClickListener)
65  *         .setAppEntity(0, appEntityInfo)
66  *         .setAppEntity(1, appEntityInfo)
67  *         .setAppEntity(2, appEntityInfo)
68  *         .apply();
69  * </pre>
70  */
71 public class AppEntitiesHeaderController {
72 
73     private static final String TAG = "AppEntitiesHeaderCtl";
74 
75     @VisibleForTesting
76     public static final int MAXIMUM_APPS = 3;
77 
78     private final Context mContext;
79     private final TextView mHeaderTitleView;
80     private final TextView mHeaderEmptyView;
81     private final Button mHeaderDetailsView;
82 
83     private final AppEntityInfo[] mAppEntityInfos;
84     private final View mAppViewsContainer;
85     private final View[] mAppEntityViews;
86     private final ImageView[] mAppIconViews;
87     private final TextView[] mAppTitleViews;
88     private final TextView[] mAppSummaryViews;
89 
90     private int mHeaderTitleRes;
91     private int mHeaderDetailsRes;
92     private int mHeaderEmptyRes;
93     private CharSequence mHeaderDetails;
94     private View.OnClickListener mDetailsOnClickListener;
95 
96     /**
97      * Creates a new instance of the controller.
98      *
99      * @param context               the Context the view is running in
100      * @param appEntitiesHeaderView view was inflated from <code>app_entities_header</code>
101      */
newInstance(@onNull Context context, @NonNull View appEntitiesHeaderView)102     public static AppEntitiesHeaderController newInstance(@NonNull Context context,
103             @NonNull View appEntitiesHeaderView) {
104         return new AppEntitiesHeaderController(context, appEntitiesHeaderView);
105     }
106 
AppEntitiesHeaderController(Context context, View appEntitiesHeaderView)107     private AppEntitiesHeaderController(Context context, View appEntitiesHeaderView) {
108         mContext = context;
109         mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title);
110         mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details);
111         mHeaderEmptyView = appEntitiesHeaderView.findViewById(R.id.empty_view);
112         mAppViewsContainer = appEntitiesHeaderView.findViewById(R.id.app_views_container);
113 
114         mAppEntityInfos = new AppEntityInfo[MAXIMUM_APPS];
115         mAppIconViews = new ImageView[MAXIMUM_APPS];
116         mAppTitleViews = new TextView[MAXIMUM_APPS];
117         mAppSummaryViews = new TextView[MAXIMUM_APPS];
118 
119         mAppEntityViews = new View[]{
120                 appEntitiesHeaderView.findViewById(R.id.app1_view),
121                 appEntitiesHeaderView.findViewById(R.id.app2_view),
122                 appEntitiesHeaderView.findViewById(R.id.app3_view)
123         };
124 
125         // Initialize view in advance, so we won't take too much time to do it when controller is
126         // binding view.
127         for (int index = 0; index < MAXIMUM_APPS; index++) {
128             final View appView = mAppEntityViews[index];
129             mAppIconViews[index] = (ImageView) appView.findViewById(R.id.app_icon);
130             mAppTitleViews[index] = (TextView) appView.findViewById(R.id.app_title);
131             mAppSummaryViews[index] = (TextView) appView.findViewById(R.id.app_summary);
132         }
133     }
134 
135     /**
136      * Set the text resource for app entities header title.
137      */
setHeaderTitleRes(@tringRes int titleRes)138     public AppEntitiesHeaderController setHeaderTitleRes(@StringRes int titleRes) {
139         mHeaderTitleRes = titleRes;
140         return this;
141     }
142 
143     /**
144      * Set the text resource for app entities header details.
145      */
setHeaderDetailsRes(@tringRes int detailsRes)146     public AppEntitiesHeaderController setHeaderDetailsRes(@StringRes int detailsRes) {
147         mHeaderDetailsRes = detailsRes;
148         return this;
149     }
150 
151     /**
152      * Set the text for app entities header details.
153      */
setHeaderDetails(CharSequence detailsText)154     public AppEntitiesHeaderController setHeaderDetails(CharSequence detailsText) {
155         mHeaderDetails = detailsText;
156         return this;
157     }
158 
159     /**
160      * Register a callback to be invoked when header details view is clicked.
161      */
setHeaderDetailsClickListener( @ullable View.OnClickListener clickListener)162     public AppEntitiesHeaderController setHeaderDetailsClickListener(
163             @Nullable View.OnClickListener clickListener) {
164         mDetailsOnClickListener = clickListener;
165         return this;
166     }
167 
168     /**
169      * Sets the string resource id for the empty text.
170      */
setHeaderEmptyRes(@tringRes int emptyRes)171     public AppEntitiesHeaderController setHeaderEmptyRes(@StringRes int emptyRes) {
172         mHeaderEmptyRes = emptyRes;
173         return this;
174     }
175 
176     /**
177      * Set an app entity at a specified position view.
178      *
179      * @param index         the index at which the specified view is to be inserted
180      * @param appEntityInfo the information of an app entity
181      * @return this {@code AppEntitiesHeaderController} object
182      */
setAppEntity(int index, @NonNull AppEntityInfo appEntityInfo)183     public AppEntitiesHeaderController setAppEntity(int index,
184             @NonNull AppEntityInfo appEntityInfo) {
185         mAppEntityInfos[index] = appEntityInfo;
186         return this;
187     }
188 
189     /**
190      * Remove an app entity at a specified position view.
191      *
192      * @param index the index at which the specified view is to be removed
193      * @return this {@code AppEntitiesHeaderController} object
194      */
removeAppEntity(int index)195     public AppEntitiesHeaderController removeAppEntity(int index) {
196         mAppEntityInfos[index] = null;
197         return this;
198     }
199 
200     /**
201      * Clear all app entities in app entities header.
202      *
203      * @return this {@code AppEntitiesHeaderController} object
204      */
clearAllAppEntities()205     public AppEntitiesHeaderController clearAllAppEntities() {
206         for (int index = 0; index < MAXIMUM_APPS; index++) {
207             removeAppEntity(index);
208         }
209         return this;
210     }
211 
212     /**
213      * Done mutating app entities header, rebinds everything.
214      */
apply()215     public void apply() {
216         bindHeaderTitleView();
217 
218         if (isAppEntityInfosEmpty()) {
219             setEmptyViewVisible(true);
220             return;
221         }
222         setEmptyViewVisible(false);
223         bindHeaderDetailsView();
224 
225         // Rebind all apps view
226         for (int index = 0; index < MAXIMUM_APPS; index++) {
227             bindAppEntityView(index);
228         }
229     }
230 
bindHeaderTitleView()231     private void bindHeaderTitleView() {
232         CharSequence titleText = "";
233         try {
234             titleText = mContext.getText(mHeaderTitleRes);
235         } catch (Resources.NotFoundException e) {
236             Log.e(TAG, "Resource of header title can't not be found!", e);
237         }
238         mHeaderTitleView.setText(titleText);
239         mHeaderTitleView.setVisibility(
240                 TextUtils.isEmpty(titleText) ? View.GONE : View.VISIBLE);
241     }
242 
bindHeaderDetailsView()243     private void bindHeaderDetailsView() {
244         CharSequence detailsText = mHeaderDetails;
245         if (TextUtils.isEmpty(detailsText)) {
246             try {
247                 detailsText = mContext.getText(mHeaderDetailsRes);
248             } catch (Resources.NotFoundException e) {
249                 Log.e(TAG, "Resource of header details can't not be found!", e);
250             }
251         }
252         mHeaderDetailsView.setText(detailsText);
253         mHeaderDetailsView.setVisibility(
254                 TextUtils.isEmpty(detailsText) ? View.GONE : View.VISIBLE);
255         mHeaderDetailsView.setOnClickListener(mDetailsOnClickListener);
256     }
257 
bindAppEntityView(int index)258     private void bindAppEntityView(int index) {
259         final AppEntityInfo appEntityInfo = mAppEntityInfos[index];
260         mAppEntityViews[index].setVisibility(appEntityInfo != null ? View.VISIBLE : View.GONE);
261 
262         if (appEntityInfo != null) {
263             mAppEntityViews[index].setOnClickListener(appEntityInfo.getClickListener());
264 
265             mAppIconViews[index].setImageDrawable(appEntityInfo.getIcon());
266 
267             final CharSequence title = appEntityInfo.getTitle();
268             mAppTitleViews[index].setVisibility(
269                     TextUtils.isEmpty(title) ? View.INVISIBLE : View.VISIBLE);
270             mAppTitleViews[index].setText(title);
271 
272             final CharSequence summary = appEntityInfo.getSummary();
273             mAppSummaryViews[index].setVisibility(
274                     TextUtils.isEmpty(summary) ? View.INVISIBLE : View.VISIBLE);
275             mAppSummaryViews[index].setText(summary);
276         }
277     }
278 
setEmptyViewVisible(boolean visible)279     private void setEmptyViewVisible(boolean visible) {
280         if (mHeaderEmptyRes != 0) {
281             mHeaderEmptyView.setText(mHeaderEmptyRes);
282         }
283         mHeaderEmptyView.setVisibility(visible ? View.VISIBLE : View.GONE);
284         mHeaderDetailsView.setVisibility(visible ? View.GONE : View.VISIBLE);
285         mAppViewsContainer.setVisibility(visible ? View.GONE : View.VISIBLE);
286     }
287 
isAppEntityInfosEmpty()288     private boolean isAppEntityInfosEmpty() {
289         for (AppEntityInfo info : mAppEntityInfos) {
290             if (info != null) {
291                 return false;
292             }
293         }
294         return true;
295     }
296 }
297