1 /*
2  * Copyright (C) 2016 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.settings.widget;
18 
19 import static com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.startAppInfoSettings;
20 
21 import android.annotation.IdRes;
22 import android.annotation.UserIdInt;
23 import android.app.Activity;
24 import android.app.settings.SettingsEnums;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageInfo;
28 import android.graphics.drawable.Drawable;
29 import android.os.UserHandle;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.widget.ImageButton;
35 import android.widget.ImageView;
36 import android.widget.TextView;
37 
38 import androidx.annotation.IntDef;
39 import androidx.annotation.VisibleForTesting;
40 import androidx.fragment.app.Fragment;
41 
42 import com.android.settings.R;
43 import com.android.settings.Utils;
44 import com.android.settings.overlay.FeatureFactory;
45 import com.android.settingslib.applications.ApplicationsState;
46 import com.android.settingslib.widget.LayoutPreference;
47 
48 import java.lang.annotation.Retention;
49 import java.lang.annotation.RetentionPolicy;
50 
51 public class EntityHeaderController {
52 
53     @IntDef({ActionType.ACTION_NONE,
54             ActionType.ACTION_NOTIF_PREFERENCE,
55             ActionType.ACTION_EDIT_PREFERENCE,})
56     @Retention(RetentionPolicy.SOURCE)
57     public @interface ActionType {
58         int ACTION_NONE = 0;
59         int ACTION_NOTIF_PREFERENCE = 1;
60         int ACTION_EDIT_PREFERENCE = 2;
61     }
62 
63     public static final String PREF_KEY_APP_HEADER = "pref_app_header";
64 
65     private static final String TAG = "AppDetailFeature";
66 
67     private final Context mAppContext;
68     private final Fragment mFragment;
69     private final int mMetricsCategory;
70     private final View mHeader;
71     private Drawable mIcon;
72     private int mPrefOrder = -1000;
73     private String mIconContentDescription;
74     private CharSequence mLabel;
75     private CharSequence mSummary;
76     // Required for hearing aid devices.
77     private CharSequence mSecondSummary;
78     private String mPackageName;
79     private Intent mAppNotifPrefIntent;
80     @UserIdInt
81     private int mUid = UserHandle.USER_NULL;
82     @ActionType
83     private int mAction1;
84     @ActionType
85     private int mAction2;
86 
87     private boolean mHasAppInfoLink;
88 
89     private boolean mIsInstantApp;
90 
91     private View.OnClickListener mEditOnClickListener;
92 
93     /**
94      * Creates a new instance of the controller.
95      *
96      * @param fragment The fragment that header will be placed in.
97      * @param header   Optional: header view if it's already created.
98      */
newInstance(Activity activity, Fragment fragment, View header)99     public static EntityHeaderController newInstance(Activity activity, Fragment fragment,
100             View header) {
101         return new EntityHeaderController(activity, fragment, header);
102     }
103 
EntityHeaderController(Activity activity, Fragment fragment, View header)104     private EntityHeaderController(Activity activity, Fragment fragment, View header) {
105         mAppContext = activity.getApplicationContext();
106         mFragment = fragment;
107         mMetricsCategory = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
108                 .getMetricsCategory(fragment);
109         if (header != null) {
110             mHeader = header;
111         } else {
112             mHeader = LayoutInflater.from(fragment.getContext())
113                     .inflate(com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header,
114                             null /* root */);
115         }
116     }
117 
118     /**
119      * Set the icon in the header. Callers should also consider calling setIconContentDescription
120      * to provide a description of this icon for accessibility purposes.
121      */
setIcon(Drawable icon)122     public EntityHeaderController setIcon(Drawable icon) {
123         if (icon != null) {
124             final Drawable.ConstantState state = icon.getConstantState();
125             mIcon = state != null ? state.newDrawable(mAppContext.getResources()) : icon;
126         }
127         return this;
128     }
129 
130     /**
131      * Convenience method to set the header icon from an ApplicationsState.AppEntry. Callers should
132      * also consider calling setIconContentDescription to provide a description of this icon for
133      * accessibility purposes.
134      */
setIcon(ApplicationsState.AppEntry appEntry)135     public EntityHeaderController setIcon(ApplicationsState.AppEntry appEntry) {
136         mIcon = Utils.getBadgedIcon(mAppContext, appEntry.info);
137         return this;
138     }
139 
setIconContentDescription(String contentDescription)140     public EntityHeaderController setIconContentDescription(String contentDescription) {
141         mIconContentDescription = contentDescription;
142         return this;
143     }
144 
setLabel(CharSequence label)145     public EntityHeaderController setLabel(CharSequence label) {
146         mLabel = label;
147         return this;
148     }
149 
setLabel(ApplicationsState.AppEntry appEntry)150     public EntityHeaderController setLabel(ApplicationsState.AppEntry appEntry) {
151         mLabel = appEntry.label;
152         return this;
153     }
154 
setSummary(CharSequence summary)155     public EntityHeaderController setSummary(CharSequence summary) {
156         mSummary = summary;
157         return this;
158     }
159 
setSummary(PackageInfo packageInfo)160     public EntityHeaderController setSummary(PackageInfo packageInfo) {
161         if (packageInfo != null) {
162             mSummary = packageInfo.versionName;
163         }
164         return this;
165     }
166 
setSecondSummary(CharSequence summary)167     public EntityHeaderController setSecondSummary(CharSequence summary) {
168         mSecondSummary = summary;
169         return this;
170     }
171 
setHasAppInfoLink(boolean hasAppInfoLink)172     public EntityHeaderController setHasAppInfoLink(boolean hasAppInfoLink) {
173         mHasAppInfoLink = hasAppInfoLink;
174         return this;
175     }
176 
setButtonActions(@ctionType int action1, @ActionType int action2)177     public EntityHeaderController setButtonActions(@ActionType int action1,
178             @ActionType int action2) {
179         mAction1 = action1;
180         mAction2 = action2;
181         return this;
182     }
183 
setPackageName(String packageName)184     public EntityHeaderController setPackageName(String packageName) {
185         mPackageName = packageName;
186         return this;
187     }
188 
setUid(int uid)189     public EntityHeaderController setUid(int uid) {
190         mUid = uid;
191         return this;
192     }
193 
setAppNotifPrefIntent(Intent appNotifPrefIntent)194     public EntityHeaderController setAppNotifPrefIntent(Intent appNotifPrefIntent) {
195         mAppNotifPrefIntent = appNotifPrefIntent;
196         return this;
197     }
198 
setIsInstantApp(boolean isInstantApp)199     public EntityHeaderController setIsInstantApp(boolean isInstantApp) {
200         mIsInstantApp = isInstantApp;
201         return this;
202     }
203 
setEditListener(View.OnClickListener listener)204     public EntityHeaderController setEditListener(View.OnClickListener listener) {
205         mEditOnClickListener = listener;
206         return this;
207     }
208 
209     /** Sets this preference order. */
setOrder(int order)210     public EntityHeaderController setOrder(int order) {
211         mPrefOrder = order;
212         return this;
213     }
214 
215     /**
216      * Done mutating entity header, rebinds everything and return a new {@link LayoutPreference}.
217      */
done(Context uiContext)218     public LayoutPreference done(Context uiContext) {
219         final LayoutPreference pref = new LayoutPreference(uiContext, done());
220         // Makes sure it's the first preference onscreen.
221         pref.setOrder(mPrefOrder);
222         pref.setSelectable(false);
223         pref.setKey(PREF_KEY_APP_HEADER);
224         pref.setAllowDividerBelow(true);
225         return pref;
226     }
227 
228     /**
229      * Done mutating entity header, rebinds everything (optionally skip rebinding buttons).
230      */
done(boolean rebindActions)231     public View done(boolean rebindActions) {
232         ImageView iconView = mHeader.findViewById(R.id.entity_header_icon);
233         if (iconView != null) {
234             iconView.setImageDrawable(mIcon);
235             iconView.setContentDescription(mIconContentDescription);
236         }
237         setText(R.id.entity_header_title, mLabel);
238         setText(R.id.entity_header_summary, mSummary);
239         setText(com.android.settingslib.widget.preference.layout.R.id.entity_header_second_summary, mSecondSummary);
240         if (mIsInstantApp) {
241             setText(com.android.settingslib.widget.preference.layout.R.id.install_type,
242                     mHeader.getResources().getString(R.string.install_type_instant));
243         }
244 
245         if (rebindActions) {
246             bindHeaderButtons();
247         }
248 
249         return mHeader;
250     }
251 
252     /**
253      * Only binds entity header with button actions.
254      */
bindHeaderButtons()255     public EntityHeaderController bindHeaderButtons() {
256         final View entityHeaderContent = mHeader.findViewById(
257                 com.android.settingslib.widget.preference.layout.R.id.entity_header_content);
258         final ImageButton button1 = mHeader.findViewById(android.R.id.button1);
259         final ImageButton button2 = mHeader.findViewById(android.R.id.button2);
260         bindAppInfoLink(entityHeaderContent);
261         bindButton(button1, mAction1);
262         bindButton(button2, mAction2);
263         return this;
264     }
265 
bindAppInfoLink(View entityHeaderContent)266     private void bindAppInfoLink(View entityHeaderContent) {
267         if (!mHasAppInfoLink) {
268             // Caller didn't ask for app link, skip.
269             return;
270         }
271         if (entityHeaderContent == null
272                 || mPackageName == null
273                 || mPackageName.equals(Utils.OS_PKG)
274                 || mUid == UserHandle.USER_NULL) {
275             Log.w(TAG, "Missing ingredients to build app info link, skip");
276             return;
277         }
278         entityHeaderContent.setOnClickListener(v -> startAppInfoSettings(
279                 mPackageName, mUid, mFragment, 0 /* request */,
280                 mMetricsCategory));
281     }
282 
283     /**
284      * Done mutating entity header, rebinds everything.
285      */
286     @VisibleForTesting
done()287     View done() {
288         return done(true /* rebindActions */);
289     }
290 
bindButton(ImageButton button, @ActionType int action)291     private void bindButton(ImageButton button, @ActionType int action) {
292         if (button == null) {
293             return;
294         }
295         switch (action) {
296             case ActionType.ACTION_EDIT_PREFERENCE: {
297                 if (mEditOnClickListener == null) {
298                     button.setVisibility(View.GONE);
299                 } else {
300                     button.setImageResource(com.android.internal.R.drawable.ic_mode_edit);
301                     button.setVisibility(View.VISIBLE);
302                     button.setOnClickListener(mEditOnClickListener);
303                 }
304                 return;
305             }
306             case ActionType.ACTION_NOTIF_PREFERENCE: {
307                 if (mAppNotifPrefIntent == null) {
308                     button.setVisibility(View.GONE);
309                 } else {
310                     button.setOnClickListener(v -> {
311                         FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
312                                 .action(SettingsEnums.PAGE_UNKNOWN,
313                                         SettingsEnums.ACTION_OPEN_APP_NOTIFICATION_SETTING,
314                                         mMetricsCategory,
315                                         null, 0);
316                         mFragment.startActivity(mAppNotifPrefIntent);
317                     });
318                     button.setVisibility(View.VISIBLE);
319                 }
320                 return;
321             }
322             case ActionType.ACTION_NONE: {
323                 button.setVisibility(View.GONE);
324                 return;
325             }
326         }
327     }
328 
setText(@dRes int id, CharSequence text)329     private void setText(@IdRes int id, CharSequence text) {
330         TextView textView = mHeader.findViewById(id);
331         if (textView != null) {
332             textView.setText(text);
333             textView.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
334         }
335     }
336 }
337