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 int mPrefOrder = -1000; 78 private String mIconContentDescription; 79 private CharSequence mLabel; 80 private CharSequence mSummary; 81 // Required for hearing aid devices. 82 private CharSequence mSecondSummary; 83 private String mPackageName; 84 private Intent mAppNotifPrefIntent; 85 @UserIdInt 86 private int mUid = UserHandle.USER_NULL; 87 @ActionType 88 private int mAction1; 89 @ActionType 90 private int mAction2; 91 92 private boolean mHasAppInfoLink; 93 94 private boolean mIsInstantApp; 95 96 private View.OnClickListener mEditOnClickListener; 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 final Drawable.ConstantState state = icon.getConstantState(); 136 mIcon = state != null ? state.newDrawable(mAppContext.getResources()) : icon; 137 } 138 return this; 139 } 140 141 /** 142 * Convenience method to set the header icon from an ApplicationsState.AppEntry. Callers should 143 * also consider calling setIconContentDescription to provide a description of this icon for 144 * accessibility purposes. 145 */ setIcon(ApplicationsState.AppEntry appEntry)146 public EntityHeaderController setIcon(ApplicationsState.AppEntry appEntry) { 147 mIcon = Utils.getBadgedIcon(mAppContext, appEntry.info); 148 return this; 149 } 150 setIconContentDescription(String contentDescription)151 public EntityHeaderController setIconContentDescription(String contentDescription) { 152 mIconContentDescription = contentDescription; 153 return this; 154 } 155 setLabel(CharSequence label)156 public EntityHeaderController setLabel(CharSequence label) { 157 mLabel = label; 158 return this; 159 } 160 setLabel(ApplicationsState.AppEntry appEntry)161 public EntityHeaderController setLabel(ApplicationsState.AppEntry appEntry) { 162 mLabel = appEntry.label; 163 return this; 164 } 165 setSummary(CharSequence summary)166 public EntityHeaderController setSummary(CharSequence summary) { 167 mSummary = summary; 168 return this; 169 } 170 setSummary(PackageInfo packageInfo)171 public EntityHeaderController setSummary(PackageInfo packageInfo) { 172 if (packageInfo != null) { 173 mSummary = packageInfo.versionName; 174 } 175 return this; 176 } 177 setSecondSummary(CharSequence summary)178 public EntityHeaderController setSecondSummary(CharSequence summary) { 179 mSecondSummary = summary; 180 return this; 181 } 182 setSecondSummary(PackageInfo packageInfo)183 public EntityHeaderController setSecondSummary(PackageInfo packageInfo) { 184 if (packageInfo != null) { 185 mSummary = packageInfo.versionName; 186 } 187 return this; 188 } 189 setHasAppInfoLink(boolean hasAppInfoLink)190 public EntityHeaderController setHasAppInfoLink(boolean hasAppInfoLink) { 191 mHasAppInfoLink = hasAppInfoLink; 192 return this; 193 } 194 setButtonActions(@ctionType int action1, @ActionType int action2)195 public EntityHeaderController setButtonActions(@ActionType int action1, 196 @ActionType int action2) { 197 mAction1 = action1; 198 mAction2 = action2; 199 return this; 200 } 201 setPackageName(String packageName)202 public EntityHeaderController setPackageName(String packageName) { 203 mPackageName = packageName; 204 return this; 205 } 206 setUid(int uid)207 public EntityHeaderController setUid(int uid) { 208 mUid = uid; 209 return this; 210 } 211 setAppNotifPrefIntent(Intent appNotifPrefIntent)212 public EntityHeaderController setAppNotifPrefIntent(Intent appNotifPrefIntent) { 213 mAppNotifPrefIntent = appNotifPrefIntent; 214 return this; 215 } 216 setIsInstantApp(boolean isInstantApp)217 public EntityHeaderController setIsInstantApp(boolean isInstantApp) { 218 mIsInstantApp = isInstantApp; 219 return this; 220 } 221 setEditListener(View.OnClickListener listener)222 public EntityHeaderController setEditListener(View.OnClickListener listener) { 223 mEditOnClickListener = listener; 224 return this; 225 } 226 227 /** Sets this preference order. */ setOrder(int order)228 public EntityHeaderController setOrder(int order) { 229 mPrefOrder = order; 230 return this; 231 } 232 233 /** 234 * Done mutating entity header, rebinds everything and return a new {@link LayoutPreference}. 235 */ done(Activity activity, Context uiContext)236 public LayoutPreference done(Activity activity, Context uiContext) { 237 final LayoutPreference pref = new LayoutPreference(uiContext, done(activity)); 238 // Makes sure it's the first preference onscreen. 239 pref.setOrder(mPrefOrder); 240 pref.setSelectable(false); 241 pref.setKey(PREF_KEY_APP_HEADER); 242 pref.setAllowDividerBelow(true); 243 return pref; 244 } 245 246 /** 247 * Done mutating entity header, rebinds everything (optionally skip rebinding buttons). 248 */ done(Activity activity, boolean rebindActions)249 public View done(Activity activity, boolean rebindActions) { 250 ImageView iconView = mHeader.findViewById(R.id.entity_header_icon); 251 if (iconView != null) { 252 iconView.setImageDrawable(mIcon); 253 iconView.setContentDescription(mIconContentDescription); 254 } 255 setText(R.id.entity_header_title, mLabel); 256 setText(R.id.entity_header_summary, mSummary); 257 setText(R.id.entity_header_second_summary, mSecondSummary); 258 if (mIsInstantApp) { 259 setText(R.id.install_type, 260 mHeader.getResources().getString(R.string.install_type_instant)); 261 } 262 263 if (rebindActions) { 264 bindHeaderButtons(); 265 } 266 267 return mHeader; 268 } 269 270 /** 271 * Only binds entity header with button actions. 272 */ bindHeaderButtons()273 public EntityHeaderController bindHeaderButtons() { 274 final View entityHeaderContent = mHeader.findViewById(R.id.entity_header_content); 275 final ImageButton button1 = mHeader.findViewById(android.R.id.button1); 276 final ImageButton button2 = mHeader.findViewById(android.R.id.button2); 277 bindAppInfoLink(entityHeaderContent); 278 bindButton(button1, mAction1); 279 bindButton(button2, mAction2); 280 return this; 281 } 282 bindAppInfoLink(View entityHeaderContent)283 private void bindAppInfoLink(View entityHeaderContent) { 284 if (!mHasAppInfoLink) { 285 // Caller didn't ask for app link, skip. 286 return; 287 } 288 if (entityHeaderContent == null 289 || mPackageName == null 290 || mPackageName.equals(Utils.OS_PKG) 291 || mUid == UserHandle.USER_NULL) { 292 Log.w(TAG, "Missing ingredients to build app info link, skip"); 293 return; 294 } 295 entityHeaderContent.setOnClickListener(new View.OnClickListener() { 296 @Override 297 public void onClick(View v) { 298 AppInfoBase.startAppInfoFragment( 299 AppInfoDashboardFragment.class, 300 mActivity.getString(R.string.application_info_label), 301 mPackageName, mUid, mFragment, 0 /* request */, 302 mMetricsCategory); 303 } 304 }); 305 return; 306 } 307 308 /** 309 * Done mutating entity header, rebinds everything. 310 */ 311 @VisibleForTesting done(Activity activity)312 View done(Activity activity) { 313 return done(activity, true /* rebindActions */); 314 } 315 bindButton(ImageButton button, @ActionType int action)316 private void bindButton(ImageButton button, @ActionType int action) { 317 if (button == null) { 318 return; 319 } 320 switch (action) { 321 case ActionType.ACTION_EDIT_PREFERENCE: { 322 if (mEditOnClickListener == null) { 323 button.setVisibility(View.GONE); 324 } else { 325 button.setImageResource(com.android.internal.R.drawable.ic_mode_edit); 326 button.setVisibility(View.VISIBLE); 327 button.setOnClickListener(mEditOnClickListener); 328 } 329 return; 330 } 331 case ActionType.ACTION_NOTIF_PREFERENCE: { 332 if (mAppNotifPrefIntent == null) { 333 button.setVisibility(View.GONE); 334 } else { 335 button.setOnClickListener(v -> { 336 FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider() 337 .action(SettingsEnums.PAGE_UNKNOWN, 338 SettingsEnums.ACTION_OPEN_APP_NOTIFICATION_SETTING, 339 mMetricsCategory, 340 null, 0); 341 mFragment.startActivity(mAppNotifPrefIntent); 342 }); 343 button.setVisibility(View.VISIBLE); 344 } 345 return; 346 } 347 case ActionType.ACTION_NONE: { 348 button.setVisibility(View.GONE); 349 return; 350 } 351 } 352 } 353 setText(@dRes int id, CharSequence text)354 private void setText(@IdRes int id, CharSequence text) { 355 TextView textView = mHeader.findViewById(id); 356 if (textView != null) { 357 textView.setText(text); 358 textView.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE); 359 } 360 } 361 } 362