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