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