• 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.ActionBar;
22 import android.app.Activity;
23 import android.app.Fragment;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.ResolveInfo;
28 import android.graphics.drawable.ColorDrawable;
29 import android.graphics.drawable.Drawable;
30 import android.os.UserHandle;
31 import android.support.annotation.IntDef;
32 import android.support.annotation.VisibleForTesting;
33 import android.support.v7.widget.RecyclerView;
34 import android.text.TextUtils;
35 import android.util.Log;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.widget.ImageButton;
39 import android.widget.ImageView;
40 import android.widget.TextView;
41 
42 import com.android.settings.R;
43 import com.android.settings.Utils;
44 import com.android.settings.applications.AppInfoBase;
45 import com.android.settings.applications.InstalledAppDetails;
46 import com.android.settings.applications.LayoutPreference;
47 import com.android.settings.overlay.FeatureFactory;
48 import com.android.settingslib.applications.ApplicationsState;
49 import com.android.settingslib.core.lifecycle.Lifecycle;
50 
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 
54 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
55         .ACTION_OPEN_APP_NOTIFICATION_SETTING;
56 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_OPEN_APP_SETTING;
57 
58 public class EntityHeaderController {
59 
60     @IntDef({ActionType.ACTION_NONE,
61             ActionType.ACTION_APP_PREFERENCE,
62             ActionType.ACTION_NOTIF_PREFERENCE})
63     @Retention(RetentionPolicy.SOURCE)
64     public @interface ActionType {
65         int ACTION_NONE = 0;
66         int ACTION_APP_PREFERENCE = 1;
67         int ACTION_NOTIF_PREFERENCE = 2;
68     }
69 
70     public static final String PREF_KEY_APP_HEADER = "pref_app_header";
71 
72     private static final String TAG = "AppDetailFeature";
73 
74     private final Context mAppContext;
75     private final Activity mActivity;
76     private final Fragment mFragment;
77     private final int mMetricsCategory;
78     private final View mHeader;
79     private Lifecycle mLifecycle;
80     private RecyclerView mRecyclerView;
81     private Drawable mIcon;
82     private String mIconContentDescription;
83     private CharSequence mLabel;
84     private CharSequence mSummary;
85     private String mPackageName;
86     private Intent mAppNotifPrefIntent;
87     @UserIdInt
88     private int mUid = UserHandle.USER_NULL;
89     @ActionType
90     private int mAction1;
91     @ActionType
92     private int mAction2;
93 
94     private boolean mHasAppInfoLink;
95 
96     private boolean mIsInstantApp;
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             mIcon = icon.getConstantState().newDrawable(mAppContext.getResources());
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         if (appEntry.icon != null) {
147             mIcon = appEntry.icon.getConstantState().newDrawable(mAppContext.getResources());
148         }
149         return this;
150     }
151 
setIconContentDescription(String contentDescription)152     public EntityHeaderController setIconContentDescription(String contentDescription) {
153         mIconContentDescription = contentDescription;
154         return this;
155     }
156 
setLabel(CharSequence label)157     public EntityHeaderController setLabel(CharSequence label) {
158         mLabel = label;
159         return this;
160     }
161 
setLabel(ApplicationsState.AppEntry appEntry)162     public EntityHeaderController setLabel(ApplicationsState.AppEntry appEntry) {
163         mLabel = appEntry.label;
164         return this;
165     }
166 
setSummary(CharSequence summary)167     public EntityHeaderController setSummary(CharSequence summary) {
168         mSummary = summary;
169         return this;
170     }
171 
setSummary(PackageInfo packageInfo)172     public EntityHeaderController setSummary(PackageInfo packageInfo) {
173         if (packageInfo != null) {
174             mSummary = packageInfo.versionName;
175         }
176         return this;
177     }
178 
setHasAppInfoLink(boolean hasAppInfoLink)179     public EntityHeaderController setHasAppInfoLink(boolean hasAppInfoLink) {
180         mHasAppInfoLink = hasAppInfoLink;
181         return this;
182     }
183 
setButtonActions(@ctionType int action1, @ActionType int action2)184     public EntityHeaderController setButtonActions(@ActionType int action1,
185             @ActionType int action2) {
186         mAction1 = action1;
187         mAction2 = action2;
188         return this;
189     }
190 
setPackageName(String packageName)191     public EntityHeaderController setPackageName(String packageName) {
192         mPackageName = packageName;
193         return this;
194     }
195 
setUid(int uid)196     public EntityHeaderController setUid(int uid) {
197         mUid = uid;
198         return this;
199     }
200 
setAppNotifPrefIntent(Intent appNotifPrefIntent)201     public EntityHeaderController setAppNotifPrefIntent(Intent appNotifPrefIntent) {
202         mAppNotifPrefIntent = appNotifPrefIntent;
203         return this;
204     }
205 
setIsInstantApp(boolean isInstantApp)206     public EntityHeaderController setIsInstantApp(boolean isInstantApp) {
207         this.mIsInstantApp = isInstantApp;
208         return this;
209     }
210 
211     /**
212      * Done mutating entity header, rebinds everything and return a new {@link LayoutPreference}.
213      */
done(Activity activity, Context uiContext)214     public LayoutPreference done(Activity activity, Context uiContext) {
215         final LayoutPreference pref = new LayoutPreference(uiContext, done(activity));
216         // Makes sure it's the first preference onscreen.
217         pref.setOrder(-1000);
218         pref.setSelectable(false);
219         pref.setKey(PREF_KEY_APP_HEADER);
220         return pref;
221     }
222 
223     /**
224      * Done mutating entity header, rebinds everything (optionally skip rebinding buttons).
225      */
done(Activity activity, boolean rebindActions)226     public View done(Activity activity, boolean rebindActions) {
227         styleActionBar(activity);
228         ImageView iconView = mHeader.findViewById(R.id.entity_header_icon);
229         if (iconView != null) {
230             iconView.setImageDrawable(mIcon);
231             iconView.setContentDescription(mIconContentDescription);
232         }
233         setText(R.id.entity_header_title, mLabel);
234         setText(R.id.entity_header_summary, mSummary);
235         if (mIsInstantApp) {
236             setText(R.id.install_type,
237                     mHeader.getResources().getString(R.string.install_type_instant));
238         }
239 
240         if (rebindActions) {
241             bindHeaderButtons();
242         }
243 
244         return mHeader;
245     }
246 
247     /**
248      * Only binds entity header with button actions.
249      */
bindHeaderButtons()250     public EntityHeaderController bindHeaderButtons() {
251         final View entityHeaderContent = mHeader.findViewById(R.id.entity_header_content);
252         final ImageButton button1 = mHeader.findViewById(android.R.id.button1);
253         final ImageButton button2 = mHeader.findViewById(android.R.id.button2);
254         bindAppInfoLink(entityHeaderContent);
255         bindButton(button1, mAction1);
256         bindButton(button2, mAction2);
257         return this;
258     }
259 
bindAppInfoLink(View entityHeaderContent)260     private void bindAppInfoLink(View entityHeaderContent) {
261         if (!mHasAppInfoLink) {
262             // Caller didn't ask for app link, skip.
263             return;
264         }
265         if (entityHeaderContent == null
266                 || mPackageName == null
267                 || mPackageName.equals(Utils.OS_PKG)
268                 || mUid == UserHandle.USER_NULL) {
269             Log.w(TAG, "Missing ingredients to build app info link, skip");
270             return;
271         }
272         entityHeaderContent.setOnClickListener(new View.OnClickListener() {
273             @Override
274             public void onClick(View v) {
275                 AppInfoBase.startAppInfoFragment(
276                         InstalledAppDetails.class, R.string.application_info_label,
277                         mPackageName, mUid, mFragment, 0 /* request */,
278                         mMetricsCategory);
279 
280             }
281         });
282         return;
283     }
284 
styleActionBar(Activity activity)285     public EntityHeaderController styleActionBar(Activity activity) {
286         if (activity == null) {
287             Log.w(TAG, "No activity, cannot style actionbar.");
288             return this;
289         }
290         final ActionBar actionBar = activity.getActionBar();
291         if (actionBar == null) {
292             Log.w(TAG, "No actionbar, cannot style actionbar.");
293             return this;
294         }
295         actionBar.setBackgroundDrawable(
296                 new ColorDrawable(Utils.getColorAttr(activity, android.R.attr.colorSecondary)));
297         actionBar.setElevation(0);
298         if (mRecyclerView != null && mLifecycle != null) {
299             ActionBarShadowController.attachToRecyclerView(mActivity, mLifecycle, mRecyclerView);
300         }
301 
302         return this;
303     }
304 
305     /**
306      * Done mutating entity header, rebinds everything.
307      */
308     @VisibleForTesting
done(Activity activity)309     View done(Activity activity) {
310         return done(activity, true /* rebindActions */);
311     }
312 
bindButton(ImageButton button, @ActionType int action)313     private void bindButton(ImageButton button, @ActionType int action) {
314         if (button == null) {
315             return;
316         }
317         switch (action) {
318             case ActionType.ACTION_NOTIF_PREFERENCE: {
319                 if (mAppNotifPrefIntent == null) {
320                     button.setVisibility(View.GONE);
321                 } else {
322                     button.setOnClickListener(new View.OnClickListener() {
323                         @Override
324                         public void onClick(View v) {
325                             FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider()
326                                     .actionWithSource(mAppContext, mMetricsCategory,
327                                             ACTION_OPEN_APP_NOTIFICATION_SETTING);
328                             mFragment.startActivity(mAppNotifPrefIntent);
329                         }
330                     });
331                     button.setVisibility(View.VISIBLE);
332                 }
333                 return;
334             }
335             case ActionType.ACTION_APP_PREFERENCE: {
336                 final Intent intent = resolveIntent(
337                         new Intent(Intent.ACTION_APPLICATION_PREFERENCES).setPackage(mPackageName));
338                 if (intent == null) {
339                     button.setImageDrawable(null);
340                     button.setVisibility(View.GONE);
341                     return;
342                 }
343                 button.setOnClickListener(new View.OnClickListener() {
344                     @Override
345                     public void onClick(View v) {
346                         FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider()
347                                 .actionWithSource(mAppContext, mMetricsCategory,
348                                         ACTION_OPEN_APP_SETTING);
349                         mFragment.startActivity(intent);
350                     }
351                 });
352                 button.setImageResource(R.drawable.ic_settings_24dp);
353                 button.setVisibility(View.VISIBLE);
354                 return;
355             }
356             case ActionType.ACTION_NONE: {
357                 button.setVisibility(View.GONE);
358                 return;
359             }
360         }
361     }
362 
resolveIntent(Intent i)363     private Intent resolveIntent(Intent i) {
364         ResolveInfo result = mAppContext.getPackageManager().resolveActivity(i, 0);
365         if (result != null) {
366             return new Intent(i.getAction())
367                     .setClassName(result.activityInfo.packageName, result.activityInfo.name);
368         }
369         return null;
370     }
371 
setText(@dRes int id, CharSequence text)372     private void setText(@IdRes int id, CharSequence text) {
373         TextView textView = mHeader.findViewById(id);
374         if (textView != null) {
375             textView.setText(text);
376             textView.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
377         }
378     }
379 }
380