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