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