Home | History | Annotate | Download | only in widget
      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      */
    104     public static EntityHeaderController newInstance(Activity activity, Fragment fragment,
    105             View header) {
    106         return new EntityHeaderController(activity, fragment, header);
    107     }
    108 
    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 
    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      */
    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      */
    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 
    152     public EntityHeaderController setIconContentDescription(String contentDescription) {
    153         mIconContentDescription = contentDescription;
    154         return this;
    155     }
    156 
    157     public EntityHeaderController setLabel(CharSequence label) {
    158         mLabel = label;
    159         return this;
    160     }
    161 
    162     public EntityHeaderController setLabel(ApplicationsState.AppEntry appEntry) {
    163         mLabel = appEntry.label;
    164         return this;
    165     }
    166 
    167     public EntityHeaderController setSummary(CharSequence summary) {
    168         mSummary = summary;
    169         return this;
    170     }
    171 
    172     public EntityHeaderController setSummary(PackageInfo packageInfo) {
    173         if (packageInfo != null) {
    174             mSummary = packageInfo.versionName;
    175         }
    176         return this;
    177     }
    178 
    179     public EntityHeaderController setHasAppInfoLink(boolean hasAppInfoLink) {
    180         mHasAppInfoLink = hasAppInfoLink;
    181         return this;
    182     }
    183 
    184     public EntityHeaderController setButtonActions(@ActionType int action1,
    185             @ActionType int action2) {
    186         mAction1 = action1;
    187         mAction2 = action2;
    188         return this;
    189     }
    190 
    191     public EntityHeaderController setPackageName(String packageName) {
    192         mPackageName = packageName;
    193         return this;
    194     }
    195 
    196     public EntityHeaderController setUid(int uid) {
    197         mUid = uid;
    198         return this;
    199     }
    200 
    201     public EntityHeaderController setAppNotifPrefIntent(Intent appNotifPrefIntent) {
    202         mAppNotifPrefIntent = appNotifPrefIntent;
    203         return this;
    204     }
    205 
    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      */
    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      */
    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      */
    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 
    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 
    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
    309     View done(Activity activity) {
    310         return done(activity, true /* rebindActions */);
    311     }
    312 
    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 
    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 
    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