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