| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.widget; |
| |
| import static com.android.internal.logging.nano.MetricsProto.MetricsEvent |
| .ACTION_OPEN_APP_NOTIFICATION_SETTING; |
| |
| import android.annotation.IdRes; |
| import android.annotation.UserIdInt; |
| import android.app.ActionBar; |
| import android.app.Activity; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageInfo; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.Bundle; |
| import android.os.UserHandle; |
| import android.text.TextUtils; |
| import android.util.IconDrawableFactory; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.widget.ImageButton; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| |
| import androidx.annotation.IntDef; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.fragment.app.Fragment; |
| import androidx.recyclerview.widget.RecyclerView; |
| |
| import com.android.settings.R; |
| import com.android.settings.Utils; |
| import com.android.settings.applications.AppInfoBase; |
| import com.android.settings.applications.LayoutPreference; |
| import com.android.settings.applications.appinfo.AppInfoDashboardFragment; |
| import com.android.settings.overlay.FeatureFactory; |
| import com.android.settingslib.applications.ApplicationsState; |
| import com.android.settingslib.core.lifecycle.Lifecycle; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| |
| public class EntityHeaderController { |
| |
| @IntDef({ActionType.ACTION_NONE, |
| ActionType.ACTION_NOTIF_PREFERENCE, |
| ActionType.ACTION_EDIT_PREFERENCE,}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface ActionType { |
| int ACTION_NONE = 0; |
| int ACTION_NOTIF_PREFERENCE = 1; |
| int ACTION_EDIT_PREFERENCE = 2; |
| } |
| |
| public static final String PREF_KEY_APP_HEADER = "pref_app_header"; |
| |
| private static final String TAG = "AppDetailFeature"; |
| |
| private final Context mAppContext; |
| private final Activity mActivity; |
| private final Fragment mFragment; |
| private final int mMetricsCategory; |
| private final View mHeader; |
| private Lifecycle mLifecycle; |
| private RecyclerView mRecyclerView; |
| private Drawable mIcon; |
| private String mIconContentDescription; |
| private CharSequence mLabel; |
| private CharSequence mSummary; |
| // Required for hearing aid devices. |
| private CharSequence mSecondSummary; |
| private String mPackageName; |
| private Intent mAppNotifPrefIntent; |
| @UserIdInt |
| private int mUid = UserHandle.USER_NULL; |
| @ActionType |
| private int mAction1; |
| @ActionType |
| private int mAction2; |
| |
| private boolean mHasAppInfoLink; |
| |
| private boolean mIsInstantApp; |
| |
| private View.OnClickListener mEditOnClickListener; |
| |
| /** |
| * Creates a new instance of the controller. |
| * |
| * @param fragment The fragment that header will be placed in. |
| * @param header Optional: header view if it's already created. |
| */ |
| public static EntityHeaderController newInstance(Activity activity, Fragment fragment, |
| View header) { |
| return new EntityHeaderController(activity, fragment, header); |
| } |
| |
| private EntityHeaderController(Activity activity, Fragment fragment, View header) { |
| mActivity = activity; |
| mAppContext = activity.getApplicationContext(); |
| mFragment = fragment; |
| mMetricsCategory = FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider() |
| .getMetricsCategory(fragment); |
| if (header != null) { |
| mHeader = header; |
| } else { |
| mHeader = LayoutInflater.from(fragment.getContext()) |
| .inflate(R.layout.settings_entity_header, null /* root */); |
| } |
| } |
| |
| public EntityHeaderController setRecyclerView(RecyclerView recyclerView, Lifecycle lifecycle) { |
| mRecyclerView = recyclerView; |
| mLifecycle = lifecycle; |
| return this; |
| } |
| |
| /** |
| * Set the icon in the header. Callers should also consider calling setIconContentDescription |
| * to provide a description of this icon for accessibility purposes. |
| */ |
| public EntityHeaderController setIcon(Drawable icon) { |
| if (icon != null) { |
| mIcon = icon.getConstantState().newDrawable(mAppContext.getResources()); |
| } |
| return this; |
| } |
| |
| /** |
| * Convenience method to set the header icon from an ApplicationsState.AppEntry. Callers should |
| * also consider calling setIconContentDescription to provide a description of this icon for |
| * accessibility purposes. |
| */ |
| public EntityHeaderController setIcon(ApplicationsState.AppEntry appEntry) { |
| mIcon = IconDrawableFactory.newInstance(mAppContext).getBadgedIcon(appEntry.info); |
| return this; |
| } |
| |
| public EntityHeaderController setIconContentDescription(String contentDescription) { |
| mIconContentDescription = contentDescription; |
| return this; |
| } |
| |
| public EntityHeaderController setLabel(CharSequence label) { |
| mLabel = label; |
| return this; |
| } |
| |
| public EntityHeaderController setLabel(ApplicationsState.AppEntry appEntry) { |
| mLabel = appEntry.label; |
| return this; |
| } |
| |
| public EntityHeaderController setSummary(CharSequence summary) { |
| mSummary = summary; |
| return this; |
| } |
| |
| public EntityHeaderController setSummary(PackageInfo packageInfo) { |
| if (packageInfo != null) { |
| mSummary = packageInfo.versionName; |
| } |
| return this; |
| } |
| |
| public EntityHeaderController setSecondSummary(CharSequence summary) { |
| mSecondSummary = summary; |
| return this; |
| } |
| |
| public EntityHeaderController setSecondSummary(PackageInfo packageInfo) { |
| if (packageInfo != null) { |
| mSummary = packageInfo.versionName; |
| } |
| return this; |
| } |
| |
| public EntityHeaderController setHasAppInfoLink(boolean hasAppInfoLink) { |
| mHasAppInfoLink = hasAppInfoLink; |
| return this; |
| } |
| |
| public EntityHeaderController setButtonActions(@ActionType int action1, |
| @ActionType int action2) { |
| mAction1 = action1; |
| mAction2 = action2; |
| return this; |
| } |
| |
| public EntityHeaderController setPackageName(String packageName) { |
| mPackageName = packageName; |
| return this; |
| } |
| |
| public EntityHeaderController setUid(int uid) { |
| mUid = uid; |
| return this; |
| } |
| |
| public EntityHeaderController setAppNotifPrefIntent(Intent appNotifPrefIntent) { |
| mAppNotifPrefIntent = appNotifPrefIntent; |
| return this; |
| } |
| |
| public EntityHeaderController setIsInstantApp(boolean isInstantApp) { |
| this.mIsInstantApp = isInstantApp; |
| return this; |
| } |
| |
| public EntityHeaderController setEditListener(View.OnClickListener listener) { |
| this.mEditOnClickListener = listener; |
| return this; |
| } |
| |
| /** |
| * Done mutating entity header, rebinds everything and return a new {@link LayoutPreference}. |
| */ |
| public LayoutPreference done(Activity activity, Context uiContext) { |
| final LayoutPreference pref = new LayoutPreference(uiContext, done(activity)); |
| // Makes sure it's the first preference onscreen. |
| pref.setOrder(-1000); |
| pref.setSelectable(false); |
| pref.setKey(PREF_KEY_APP_HEADER); |
| return pref; |
| } |
| |
| /** |
| * Done mutating entity header, rebinds everything (optionally skip rebinding buttons). |
| */ |
| public View done(Activity activity, boolean rebindActions) { |
| styleActionBar(activity); |
| ImageView iconView = mHeader.findViewById(R.id.entity_header_icon); |
| if (iconView != null) { |
| iconView.setImageDrawable(mIcon); |
| iconView.setContentDescription(mIconContentDescription); |
| } |
| setText(R.id.entity_header_title, mLabel); |
| setText(R.id.entity_header_summary, mSummary); |
| setText(R.id.entity_header_second_summary, mSecondSummary); |
| if (mIsInstantApp) { |
| setText(R.id.install_type, |
| mHeader.getResources().getString(R.string.install_type_instant)); |
| } |
| |
| if (rebindActions) { |
| bindHeaderButtons(); |
| } |
| |
| return mHeader; |
| } |
| |
| /** |
| * Only binds entity header with button actions. |
| */ |
| public EntityHeaderController bindHeaderButtons() { |
| final View entityHeaderContent = mHeader.findViewById(R.id.entity_header_content); |
| final ImageButton button1 = mHeader.findViewById(android.R.id.button1); |
| final ImageButton button2 = mHeader.findViewById(android.R.id.button2); |
| bindAppInfoLink(entityHeaderContent); |
| bindButton(button1, mAction1); |
| bindButton(button2, mAction2); |
| return this; |
| } |
| |
| private void bindAppInfoLink(View entityHeaderContent) { |
| if (!mHasAppInfoLink) { |
| // Caller didn't ask for app link, skip. |
| return; |
| } |
| if (entityHeaderContent == null |
| || mPackageName == null |
| || mPackageName.equals(Utils.OS_PKG) |
| || mUid == UserHandle.USER_NULL) { |
| Log.w(TAG, "Missing ingredients to build app info link, skip"); |
| return; |
| } |
| entityHeaderContent.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| AppInfoBase.startAppInfoFragment( |
| AppInfoDashboardFragment.class, R.string.application_info_label, |
| mPackageName, mUid, mFragment, 0 /* request */, |
| mMetricsCategory); |
| } |
| }); |
| return; |
| } |
| |
| /** |
| * Styles the action bar (elevation, scrolling behaviors, color, etc). |
| * <p/> |
| * This method must be called after {@link Fragment#onCreate(Bundle)}. |
| */ |
| public EntityHeaderController styleActionBar(Activity activity) { |
| if (activity == null) { |
| Log.w(TAG, "No activity, cannot style actionbar."); |
| return this; |
| } |
| final ActionBar actionBar = activity.getActionBar(); |
| if (actionBar == null) { |
| Log.w(TAG, "No actionbar, cannot style actionbar."); |
| return this; |
| } |
| actionBar.setBackgroundDrawable( |
| new ColorDrawable( |
| Utils.getColorAttrDefaultColor(activity, android.R.attr.colorPrimary))); |
| actionBar.setElevation(0); |
| if (mRecyclerView != null && mLifecycle != null) { |
| ActionBarShadowController.attachToRecyclerView(mActivity, mLifecycle, mRecyclerView); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Done mutating entity header, rebinds everything. |
| */ |
| @VisibleForTesting |
| View done(Activity activity) { |
| return done(activity, true /* rebindActions */); |
| } |
| |
| private void bindButton(ImageButton button, @ActionType int action) { |
| if (button == null) { |
| return; |
| } |
| switch (action) { |
| case ActionType.ACTION_EDIT_PREFERENCE: { |
| if (mEditOnClickListener == null) { |
| button.setVisibility(View.GONE); |
| } else { |
| button.setImageResource(R.drawable.ic_mode_edit); |
| button.setVisibility(View.VISIBLE); |
| button.setOnClickListener(mEditOnClickListener); |
| } |
| return; |
| } |
| case ActionType.ACTION_NOTIF_PREFERENCE: { |
| if (mAppNotifPrefIntent == null) { |
| button.setVisibility(View.GONE); |
| } else { |
| button.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider() |
| .actionWithSource(mAppContext, mMetricsCategory, |
| ACTION_OPEN_APP_NOTIFICATION_SETTING); |
| mFragment.startActivity(mAppNotifPrefIntent); |
| } |
| }); |
| button.setVisibility(View.VISIBLE); |
| } |
| return; |
| } |
| case ActionType.ACTION_NONE: { |
| button.setVisibility(View.GONE); |
| return; |
| } |
| } |
| } |
| |
| |
| private void setText(@IdRes int id, CharSequence text) { |
| TextView textView = mHeader.findViewById(id); |
| if (textView != null) { |
| textView.setText(text); |
| textView.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE); |
| } |
| } |
| } |