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