/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package com.linkbubble.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;
import com.linkbubble.MainApplication;
import com.linkbubble.R;
import com.linkbubble.Settings;
import com.linkbubble.util.ActionItem;
import com.linkbubble.util.Util;
import java.util.ArrayList;
import java.util.List;
public class OpenInAppButton extends ContentViewButton implements View.OnClickListener {
private static final String TAG = "OpenInAppButton";
private OnOpenInAppClickListener mOnOpenInAppClickListener;
private static final int NUM_ITEMS_IN_PREVIEW = 2;
private List<ContentView.AppForUrl> mAppsForUrl = new ArrayList<ContentView.AppForUrl>();
private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0);
private int mAppStackPadding;
private int mAppStackPreviewSize;
private int mIntrinsicIconSize;
private float mBaselineIconScale;
private int mBaselineIconSize;
private int mAvailableSpaceInPreview;
private int mPreviewXOffset;
private int mPreviewYOffset;
private float mMaxPerspectiveShift;
// The amount of vertical spread between items in the stack [0...1]
private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f;
// The degree to which the item in the back of the stack is scaled [0...1]
// (0 means it's not scaled at all, 1 means it's scaled to nothing)
private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
interface OnOpenInAppClickListener {
void onAppOpened();
}
public OpenInAppButton(Context context) {
this(context, null);
}
public OpenInAppButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public OpenInAppButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
Resources resources = context.getResources();
mAppStackPreviewSize = resources.getDimensionPixelSize(R.dimen.content_view_button_max_height);
mAppStackPadding = resources.getDimensionPixelSize(R.dimen.app_stack_padding);
mPreviewXOffset = resources.getDimensionPixelSize(R.dimen.app_stack_x_offset);
mPreviewYOffset = resources.getDimensionPixelSize(R.dimen.app_stack_y_offset);
setOnClickListener(this);
}
void setOnOpenInAppClickListener(OnOpenInAppClickListener listener) {
mOnOpenInAppClickListener = listener;
}
boolean configure(List<ContentView.AppForUrl> appsForUrl) {
mAppsForUrl.clear();
for (ContentView.AppForUrl appForUrl : appsForUrl) {
if (Util.isLinkBubbleResolveInfo(appForUrl.mResolveInfo) == false) {
mAppsForUrl.add(appForUrl);
}
}
int appsForUrlSize = mAppsForUrl.size();
if (appsForUrlSize == 1) {
ContentView.AppForUrl appForUrl = appsForUrl.get(0);
Drawable d = appForUrl.getIcon(getContext());
if (d != null) {
setImageDrawable(d);
setVisibility(VISIBLE);
setTag(appForUrl);
return true;
}
} else if (appsForUrlSize > 1) {
setVisibility(VISIBLE);
setImageDrawable(null);
return true;
}
setVisibility(GONE);
return false;
}
private void computePreviewDrawingParams(int drawableSize) {
if (mIntrinsicIconSize != drawableSize) {
mIntrinsicIconSize = drawableSize;
computePreviewDrawingParams();
}
}
void computePreviewDrawingParams() {
final int previewSize = mAppStackPreviewSize;
final int previewPadding = mAppStackPadding;
mAvailableSpaceInPreview = (previewSize - 2 * previewPadding);
// cos(45) = 0.707 + ~= 0.1) = 0.8f
int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale);
mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
}
private void computePreviewDrawingParams(Drawable d) {
computePreviewDrawingParams(d.getIntrinsicWidth());
}
class PreviewItemDrawingParams {
PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) {
this.transX = transX;
this.transY = transY;
this.scale = scale;
this.overlayAlpha = overlayAlpha;
}
float transX;
float transY;
float scale;
int overlayAlpha;
Drawable drawable;
}
@Override
protected void dispatchDraw(android.graphics.Canvas canvas) {
super.dispatchDraw(canvas);
if (mAppsForUrl == null || mAppsForUrl.size() <= 1) {
return;
}
Context context = getContext();
Drawable d = mAppsForUrl.get(0).getIcon(context);
if (d != null) {
computePreviewDrawingParams(d);
}
if (mIsTouched) {
canvas.drawColor(sTouchedColor);
}
int nItemsInPreview = Math.min(mAppsForUrl.size(), NUM_ITEMS_IN_PREVIEW);
for (int i = nItemsInPreview - 1; i >= 0; i--) {
d = mAppsForUrl.get(i).getIcon(context);
mParams = computePreviewItemDrawingParams(i, mParams);
mParams.drawable = d;
drawPreviewItem(canvas, mParams);
}
}
private PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
PreviewItemDrawingParams params) {
index = NUM_ITEMS_IN_PREVIEW - index - 1;
float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1);
float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
float offset = (1 - r) * mMaxPerspectiveShift;
float scaledSize = scale * mBaselineIconSize;
float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize;
// We want to imagine our coordinates from the bottom left, growing up and to the
// right. This is natural for the x-axis, but for the y-axis, we have to invert things.
//float cellScale = preferences.getWorkspaceCellScale();
float previewXOffset = mPreviewXOffset;
float previewYOffset = mPreviewYOffset;
float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + previewYOffset;
float transX = offset + scaleOffsetCorrection + previewXOffset;
float totalScale = mBaselineIconScale * scale;
final int overlayAlpha = (int) (80 * (1 - r));
if (params == null) {
params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
} else {
params.transX = transX;
params.transY = transY;
params.scale = totalScale;
params.overlayAlpha = overlayAlpha;
}
return params;
}
private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
canvas.save();
canvas.translate(params.transX, params.transY);
canvas.scale(params.scale, params.scale);
Drawable d = params.drawable;
if (d != null) {
d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
d.setFilterBitmap(true);
d.setColorFilter(Color.argb(params.overlayAlpha, 0, 0, 0), PorterDuff.Mode.SRC_ATOP);
d.draw(canvas);
d.clearColorFilter();
d.setFilterBitmap(false);
}
canvas.restore();
}
@Override
public void onClick(View v) {
if (v.getTag() instanceof ContentView.AppForUrl) {
ContentView.AppForUrl appForUrl = (ContentView.AppForUrl)v.getTag();
if (MainApplication.loadIntent(getContext(), appForUrl.mResolveInfo.activityInfo.packageName,
appForUrl.mResolveInfo.activityInfo.name, appForUrl.mUrl.toString(), -1, true)) {
if (mOnOpenInAppClickListener != null) {
mOnOpenInAppClickListener.onAppOpened();
}
}
} else {
if (mAppsForUrl != null && mAppsForUrl.size() > 1) {
ArrayList<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();
for (ContentView.AppForUrl item : mAppsForUrl) {
resolveInfos.add(item.mResolveInfo);
}
if (0 != resolveInfos.size()) {
AlertDialog dialog = ActionItem.getActionItemPickerAlert(getContext(), resolveInfos, R.string.pick_default_app,
new ActionItem.OnActionItemDefaultSelectedListener() {
@Override
public void onSelected(ActionItem actionItem, boolean always) {
ContentView.AppForUrl appForUrl = getAppForUrl(actionItem.mPackageName, actionItem.mActivityClassName);
if (appForUrl != null) {
if (always) {
Settings.get().setDefaultApp(appForUrl.mUrl.toString(), appForUrl.mResolveInfo);
}
if (MainApplication.loadIntent(getContext(), appForUrl.mResolveInfo.activityInfo.packageName,
appForUrl.mResolveInfo.activityInfo.name, appForUrl.mUrl.toString(), -1, true)) {
if (mOnOpenInAppClickListener != null) {
mOnOpenInAppClickListener.onAppOpened();
}
}
}
}
});
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
Util.showThemedDialog(dialog);
}
}
}
}
private ContentView.AppForUrl getAppForUrl(String packageName, String className) {
for (ContentView.AppForUrl appForUrl : mAppsForUrl) {
if (appForUrl.mResolveInfo.activityInfo.packageName.equals(packageName)
&& appForUrl.mResolveInfo.activityInfo.name.equals(className)) {
return appForUrl;
}
}
return null;
}
}