| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.customtabs; |
| |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.Bundle; |
| import android.support.annotation.NonNull; |
| import android.support.customtabs.CustomTabsIntent; |
| import android.text.TextUtils; |
| import android.view.Gravity; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.View.OnLongClickListener; |
| import android.view.ViewGroup; |
| import android.widget.ImageButton; |
| |
| import org.chromium.base.Log; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.util.IntentUtils; |
| import org.chromium.chrome.browser.widget.TintedDrawable; |
| import org.chromium.ui.widget.Toast; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * Container for all parameters related to creating a customizable button. |
| */ |
| class CustomButtonParams { |
| private static final String TAG = "CustomTabs"; |
| |
| private final PendingIntent mPendingIntent; |
| private int mId; |
| private Bitmap mIcon; |
| private String mDescription; |
| private boolean mShouldTint; |
| private boolean mIsOnToolbar; |
| |
| private CustomButtonParams(int id, Bitmap icon, String description, |
| @Nullable PendingIntent pendingIntent, boolean tinted, boolean onToolbar) { |
| mId = id; |
| mIcon = icon; |
| mDescription = description; |
| mPendingIntent = pendingIntent; |
| mShouldTint = tinted; |
| mIsOnToolbar = onToolbar; |
| } |
| |
| /** |
| * Replaces the current icon and description with new ones. |
| */ |
| void update(@NonNull Bitmap icon, @NonNull String description) { |
| mIcon = icon; |
| mDescription = description; |
| } |
| |
| /** |
| * @return Whether this button should be shown on the toolbar. |
| */ |
| boolean showOnToolbar() { |
| return mIsOnToolbar; |
| } |
| |
| /** |
| * @return The id associated with this button. The custom button on the toolbar always uses |
| * {@link CustomTabsIntent#TOOLBAR_ACTION_BUTTON_ID} as id. |
| */ |
| int getId() { |
| return mId; |
| } |
| |
| /** |
| * @return The drawable for the customized button. |
| */ |
| Drawable getIcon(Resources res) { |
| if (mShouldTint) { |
| return TintedDrawable.constructTintedDrawable(res, mIcon); |
| } else { |
| return new BitmapDrawable(res, mIcon); |
| } |
| } |
| |
| /** |
| * @return The content description for the customized button. |
| */ |
| String getDescription() { |
| return mDescription; |
| } |
| |
| /** |
| * @return The {@link PendingIntent} that will be sent when user clicks the customized button. |
| */ |
| PendingIntent getPendingIntent() { |
| return mPendingIntent; |
| } |
| |
| /** |
| * Builds an {@link ImageButton} from the data in this params. Generated buttons should be |
| * placed on the bottom bar. The button's tag will be its id. |
| * @param parent The parent that the inflated {@link ImageButton}. |
| * @param listener {@link OnClickListener} that should be used with the button. |
| * @return Parsed list of {@link CustomButtonParams}, which is empty if the input is invalid. |
| */ |
| ImageButton buildBottomBarButton(Context context, ViewGroup parent, OnClickListener listener) { |
| if (mIsOnToolbar) return null; |
| |
| ImageButton button = (ImageButton) LayoutInflater.from(context) |
| .inflate(R.layout.custom_tabs_bottombar_item, parent, false); |
| button.setId(mId); |
| button.setImageBitmap(mIcon); |
| button.setContentDescription(mDescription); |
| if (mPendingIntent == null) { |
| button.setEnabled(false); |
| } else { |
| button.setOnClickListener(listener); |
| } |
| button.setOnLongClickListener(new OnLongClickListener() { |
| @Override |
| public boolean onLongClick(View view) { |
| final int screenWidth = view.getResources().getDisplayMetrics().widthPixels; |
| final int[] screenPos = new int[2]; |
| view.getLocationOnScreen(screenPos); |
| final int width = view.getWidth(); |
| |
| Toast toast = Toast.makeText( |
| view.getContext(), view.getContentDescription(), Toast.LENGTH_SHORT); |
| toast.setGravity(Gravity.BOTTOM | Gravity.END, |
| screenWidth - screenPos[0] - width / 2, |
| view.getResources().getDimensionPixelSize( |
| R.dimen.toolbar_height_no_shadow)); |
| toast.show(); |
| return true; |
| } |
| }); |
| return button; |
| } |
| |
| /** |
| * Parses a list of {@link CustomButtonParams} from the intent sent by clients. |
| * @param intent The intent sent by the client. |
| * @return A list of parsed {@link CustomButtonParams}. Return an empty list if input is invalid |
| */ |
| static List<CustomButtonParams> fromIntent(Context context, Intent intent) { |
| List<CustomButtonParams> paramsList = new ArrayList<>(1); |
| if (intent == null) return paramsList; |
| |
| Bundle singleBundle = IntentUtils.safeGetBundleExtra(intent, |
| CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE); |
| ArrayList<Bundle> bundleList = IntentUtils.getParcelableArrayListExtra(intent, |
| CustomTabsIntent.EXTRA_ACTION_BAR_ITEMS); |
| boolean tinted = IntentUtils.safeGetBooleanExtra(intent, |
| CustomTabsIntent.EXTRA_TINT_ACTION_BUTTON, false); |
| if (singleBundle != null) paramsList.add(fromBundle(context, singleBundle, tinted, false)); |
| if (bundleList != null) { |
| Set<Integer> ids = new HashSet<>(); |
| for (Bundle bundle : bundleList) { |
| CustomButtonParams params = fromBundle(context, bundle, tinted, true); |
| if (params == null) { |
| continue; |
| } else if (ids.contains(params.getId())) { |
| Log.e(TAG, "Bottom bar items contain duplicate id: " + params.getId()); |
| continue; |
| } |
| ids.add(params.getId()); |
| paramsList.add(params); |
| } |
| } |
| return paramsList; |
| } |
| |
| /** |
| * Parses params out of a bundle. Note if a custom button contains a bitmap that does not fit |
| * into the toolbar, it will be put to the bottom bar. |
| * @param fromList Whether the bundle is contained in a list or it is the single bundle that |
| * directly comes from the intent. |
| */ |
| private static CustomButtonParams fromBundle(Context context, Bundle bundle, boolean tinted, |
| boolean fromList) { |
| if (bundle == null) return null; |
| |
| if (fromList && !bundle.containsKey(CustomTabsIntent.KEY_ID)) return null; |
| int id = IntentUtils.safeGetInt(bundle, CustomTabsIntent.KEY_ID, |
| CustomTabsIntent.TOOLBAR_ACTION_BUTTON_ID); |
| |
| Bitmap bitmap = parseBitmapFromBundle(bundle); |
| if (bitmap == null) { |
| Log.e(TAG, "Invalid action button: bitmap not present in bundle!"); |
| return null; |
| } |
| |
| String description = parseDescriptionFromBundle(bundle); |
| if (TextUtils.isEmpty(description)) { |
| Log.e(TAG, "Invalid action button: content description not present in bundle!"); |
| bitmap.recycle(); |
| return null; |
| } |
| |
| boolean onToolbar = id == CustomTabsIntent.TOOLBAR_ACTION_BUTTON_ID; |
| if (onToolbar && !doesIconFitToolbar(context, bitmap)) { |
| onToolbar = false; |
| Log.w(TAG, "Button's icon not suitable for toolbar, putting it to bottom bar instead." |
| + "See: https://developer.android.com/reference/android/support/customtabs/" |
| + "CustomTabsIntent.html#KEY_ICON"); |
| } |
| |
| PendingIntent pi = IntentUtils.safeGetParcelable(bundle, |
| CustomTabsIntent.KEY_PENDING_INTENT); |
| // PendingIntent is a must for buttons on the toolbar, but it's optional for bottom bar. |
| if (onToolbar && pi == null) { |
| Log.w(TAG, "Invalid action button on toolbar: pending intent not present in bundle!"); |
| bitmap.recycle(); |
| return null; |
| } |
| |
| return new CustomButtonParams(id, bitmap, description, pi, tinted, onToolbar); |
| } |
| |
| /** |
| * @return The bitmap contained in the given {@link Bundle}. Will return null if input is |
| * invalid. |
| */ |
| static Bitmap parseBitmapFromBundle(Bundle bundle) { |
| if (bundle == null) return null; |
| Bitmap bitmap = IntentUtils.safeGetParcelable(bundle, CustomTabsIntent.KEY_ICON); |
| if (bitmap == null) return null; |
| return bitmap; |
| } |
| |
| /** |
| * @return The content description contained in the given {@link Bundle}. Will return null if |
| * input is invalid. |
| */ |
| static String parseDescriptionFromBundle(Bundle bundle) { |
| if (bundle == null) return null; |
| String description = IntentUtils.safeGetString(bundle, CustomTabsIntent.KEY_DESCRIPTION); |
| if (TextUtils.isEmpty(description)) return null; |
| return description; |
| } |
| |
| /** |
| * @return Whether the given icon's size is suitable to put on toolbar. |
| */ |
| static boolean doesIconFitToolbar(Context context, Bitmap bitmap) { |
| int height = context.getResources().getDimensionPixelSize(R.dimen.toolbar_icon_height); |
| if (bitmap.getHeight() < height) return false; |
| int scaledWidth = bitmap.getWidth() / bitmap.getHeight() * height; |
| if (scaledWidth > 2 * height) return false; |
| return true; |
| } |
| } |