15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// found in the LICENSE file. 45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)package org.chromium.chrome.browser.banners; 65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.animation.ObjectAnimator; 8effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.app.Activity; 9effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.app.PendingIntent; 10effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.content.ActivityNotFoundException; 11effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.content.ContentResolver; 125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.content.Context; 13effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.content.Intent; 14effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.content.IntentSender; 15effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.content.pm.PackageManager; 16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import android.content.res.Configuration; 175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.content.res.Resources; 18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import android.graphics.Rect; 19effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.os.Looper; 205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.util.AttributeSet; 21effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.util.Log; 225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.view.LayoutInflater; 23a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import android.view.MotionEvent; 245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.view.View; 25effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.view.ViewConfiguration; 26a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import android.view.ViewGroup; 275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.widget.Button; 28effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport android.widget.ImageButton; 295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.widget.ImageView; 305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import android.widget.TextView; 315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import org.chromium.base.ApiCompatibilityUtils; 335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import org.chromium.chrome.R; 345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liuimport org.chromium.content.browser.ContentViewCore; 355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import org.chromium.ui.base.LocalizationUtils; 36effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport org.chromium.ui.base.WindowAndroid; 37effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport org.chromium.ui.base.WindowAndroid.IntentCallback; 385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)/** 405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Lays out a banner for showing info about an app on the Play Store. 41a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * The banner mimics the appearance of a Google Now card using a background Drawable with a shadow. 42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * PADDING CALCULATIONS 44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * The banner has three different types of padding that need to be accounted for: 45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 1) The background Drawable of the banner looks like card with a drop shadow. The Drawable 46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * defines a padding around the card that solely encompasses the space occupied by the drop 47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * shadow. 48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 2) The card itself needs to have padding so that the widgets don't abut the borders of the card. 49a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * This is defined as mPaddingCard, and is equally applied to all four sides. 50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 3) Controls other than the icon are further constrained by mPaddingControls, which applies only 51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * to the bottom and end margins. 52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * See {@link #AppBannerView.onMeasure(int, int)} for details. 53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 54a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * MARGIN CALCULATIONS 55a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Margin calculations for the banner are complicated by the background Drawable's drop shadows, 56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * since the drop shadows are meant to be counted as being part of the margin. To deal with this, 57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * the margins are calculated by deducting the background Drawable's padding from the margins 58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * defined by the XML files. 59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * EVEN MORE LAYOUT QUIRKS 61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * The layout of the banner, which includes its widget sizes, may change when the screen is rotated 62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * to account for less screen real estate. This means that all of the View's widgets and cached 63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * dimensions must be rebuilt from scratch. 645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 65effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochpublic class AppBannerView extends SwipableOverlayView 66effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch implements View.OnClickListener, InstallerDelegate.Observer, IntentCallback { 67effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private static final String TAG = "AppBannerView"; 68effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Class that is alerted about things happening to the BannerView. 715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) public static interface Observer { 735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 74effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Called when the banner is removed from the hierarchy. 75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param banner Banner being dismissed. 765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 77effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch public void onBannerRemoved(AppBannerView banner); 785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 80effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Called when the user manually closes a banner. 81effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * @param banner Banner being blocked. 82effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * @param url URL of the page that requested the banner. 83effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * @param packageName Name of the app's package. 84effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch */ 85effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch public void onBannerBlocked(AppBannerView banner, String url, String packageName); 86effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 87effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch /** 88effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Called when the banner begins to be dismissed. 89effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * @param banner Banner being closed. 90effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * @param dismissType Type of dismissal performed. 91effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch */ 92effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch public void onBannerDismissEvent(AppBannerView banner, int dismissType); 93effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 94effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch /** 95effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Called when an install event has occurred. 965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 97effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch public void onBannerInstallEvent(AppBannerView banner, int eventType); 985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 100effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Called when the banner needs to have an Activity started for a result. 101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param banner Banner firing the event. 102effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * @param intent Intent to fire. 103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 104effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch public boolean onFireIntent(AppBannerView banner, PendingIntent intent); 105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 107effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Installation states. 108effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private static final int INSTALL_STATE_NOT_INSTALLED = 0; 109effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private static final int INSTALL_STATE_INSTALLING = 1; 110effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private static final int INSTALL_STATE_INSTALLED = 2; 111effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // XML layout for the BannerView. 1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private static final int BANNER_LAYOUT = R.layout.app_banner_view; 1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // True if the layout is in left-to-right layout mode (regular mode). 1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private final boolean mIsLayoutLTR; 1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 118a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Class to alert about BannerView events. 119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private AppBannerView.Observer mObserver; 1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 121effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Information about the package. Shouldn't ever be null after calling {@link #initialize()}. 122effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private AppData mAppData; 123effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Views comprising the app banner. 1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private ImageView mIconView; 1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) private TextView mTitleView; 127effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private Button mInstallButtonView; 128a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private RatingView mRatingView; 129a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private View mLogoView; 130a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private View mBannerHighlightView; 131effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private ImageButton mCloseButtonView; 1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 133a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Dimension values. 134a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private int mDefinedMaxWidth; 135a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private int mPaddingCard; 136a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private int mPaddingControls; 137a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private int mMarginLeft; 138a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private int mMarginRight; 139a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private int mMarginBottom; 140effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private int mTouchSlop; 141a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 142a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Highlight variables. 143a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private boolean mIsBannerPressed; 144a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private float mInitialXForHighlight; 145a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 146a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Initial padding values. 147a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private final Rect mBackgroundDrawablePadding; 1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 149effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Install tracking. 150effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private boolean mWasInstallDialogShown; 151effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private InstallerDelegate mInstallTask; 152effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch private int mInstallState; 153effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Creates a BannerView and adds it to the given ContentViewCore. 1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param contentViewCore ContentViewCore to display the AppBannerView for. 157a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param observer Class that is alerted for AppBannerView events. 158a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param data Data about the app. 159a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @return The created banner. 1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu public static AppBannerView create( 1621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci ContentViewCore contentViewCore, Observer observer, AppData data) { 1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu Context context = contentViewCore.getContext().getApplicationContext(); 1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) AppBannerView banner = 1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) (AppBannerView) LayoutInflater.from(context).inflate(BANNER_LAYOUT, null); 166a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) banner.initialize(observer, data); 1675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu banner.addToView(contentViewCore); 1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return banner; 1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Creates a BannerView from an XML layout. 1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) public AppBannerView(Context context, AttributeSet attrs) { 1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) super(context, attrs); 176a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mIsLayoutLTR = !LocalizationUtils.isLayoutRtl(); 177a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 178a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Store the background Drawable's padding. The background used for banners is a 9-patch, 179a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // which means that it already defines padding. We need to take it into account when adding 180a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // even more padding to the inside of it. 181a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBackgroundDrawablePadding = new Rect(); 182a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBackgroundDrawablePadding.left = ApiCompatibilityUtils.getPaddingStart(this); 183a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBackgroundDrawablePadding.right = ApiCompatibilityUtils.getPaddingEnd(this); 184a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBackgroundDrawablePadding.top = getPaddingTop(); 185a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBackgroundDrawablePadding.bottom = getPaddingBottom(); 186effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 187effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallState = INSTALL_STATE_NOT_INSTALLED; 1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 1915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Initialize the banner with information about the package. 192a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param observer Class to alert about changes to the banner. 193a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param data Information about the app being advertised. 1945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 195a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private void initialize(Observer observer, AppData data) { 1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mObserver = observer; 197a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mAppData = data; 198a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) initializeControls(); 199a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 200a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 201a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private void initializeControls() { 202a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Cache the banner dimensions, adjusting margins for drop shadows defined in the background 203a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Drawable. 204a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) Resources res = getResources(); 205a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mDefinedMaxWidth = res.getDimensionPixelSize(R.dimen.app_banner_max_width); 206a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mPaddingCard = res.getDimensionPixelSize(R.dimen.app_banner_padding); 207a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mPaddingControls = res.getDimensionPixelSize(R.dimen.app_banner_padding_controls); 208a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mMarginLeft = res.getDimensionPixelSize(R.dimen.app_banner_margin_sides) 209a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) - mBackgroundDrawablePadding.left; 210a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mMarginRight = res.getDimensionPixelSize(R.dimen.app_banner_margin_sides) 211a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) - mBackgroundDrawablePadding.right; 212a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mMarginBottom = res.getDimensionPixelSize(R.dimen.app_banner_margin_bottom) 213a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) - mBackgroundDrawablePadding.bottom; 214a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (getLayoutParams() != null) { 215a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) MarginLayoutParams params = (MarginLayoutParams) getLayoutParams(); 216a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) params.leftMargin = mMarginLeft; 217a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) params.rightMargin = mMarginRight; 218a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) params.bottomMargin = mMarginBottom; 219a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Pull out all of the controls we are expecting. 2225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mIconView = (ImageView) findViewById(R.id.app_icon); 2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mTitleView = (TextView) findViewById(R.id.app_title); 224effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallButtonView = (Button) findViewById(R.id.app_install_button); 225a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mRatingView = (RatingView) findViewById(R.id.app_rating); 226a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mLogoView = findViewById(R.id.store_logo); 227a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBannerHighlightView = findViewById(R.id.banner_highlight); 228effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mCloseButtonView = (ImageButton) findViewById(R.id.close_button); 229a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) assert mIconView != null; 2315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) assert mTitleView != null; 232effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch assert mInstallButtonView != null; 2335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) assert mLogoView != null; 2345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) assert mRatingView != null; 235a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) assert mBannerHighlightView != null; 236effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch assert mCloseButtonView != null; 2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 238effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Set up the buttons to fire an event. 239effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallButtonView.setOnClickListener(this); 240effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mCloseButtonView.setOnClickListener(this); 2415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 2425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Configure the controls with the package information. 243a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mTitleView.setText(mAppData.title()); 244a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mIconView.setImageDrawable(mAppData.icon()); 245a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mRatingView.initialize(mAppData.rating()); 246c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch setAccessibilityInformation(); 2475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 248effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Determine how much the user can drag sideways before their touch is considered a scroll. 249effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 250effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 251effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Set up the install button. 252a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch updateButtonStatus(); 2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 255c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch /** 256c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch * Creates a succinct description about the app being advertised. 257c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch */ 258c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch private void setAccessibilityInformation() { 259c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch String bannerText = getContext().getString( 260c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch R.string.app_banner_view_accessibility, mAppData.title(), mAppData.rating()); 261c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch setContentDescription(bannerText); 262c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch } 263c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 2645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 2655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) public void onClick(View view) { 266effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mObserver == null) return; 267effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 268effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Only allow the button to be clicked when the banner's in a neutral position. 269effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (Math.abs(getTranslationX()) > ZERO_THRESHOLD 270effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch || Math.abs(getTranslationY()) > ZERO_THRESHOLD) { 271effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return; 272effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 273effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 274effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (view == mInstallButtonView) { 275a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch // Check that nothing happened in the background to change the install state of the app. 276a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch int previousState = mInstallState; 277a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch updateButtonStatus(); 278a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch if (mInstallState != previousState) return; 279a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 280effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Ignore button clicks when the app is installing. 281effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mInstallState == INSTALL_STATE_INSTALLING) return; 282effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 283effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallButtonView.setEnabled(false); 284effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 285effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mInstallState == INSTALL_STATE_NOT_INSTALLED) { 286effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // The user initiated an install. Track it happening only once. 287effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (!mWasInstallDialogShown) { 288effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mObserver.onBannerInstallEvent(this, AppBannerMetricsIds.INSTALL_TRIGGERED); 289effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mWasInstallDialogShown = true; 290effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 291effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 292effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mObserver.onFireIntent(this, mAppData.installIntent())) { 293effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Temporarily hide the banner. 294effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch createVerticalSnapAnimation(false); 295effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } else { 296effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch Log.e(TAG, "Failed to fire install intent."); 297effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch dismiss(AppBannerMetricsIds.DISMISS_ERROR); 298effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 299effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } else if (mInstallState == INSTALL_STATE_INSTALLED) { 300effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // The app is installed. Open it. 301effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch try { 302a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch Intent appIntent = getAppLaunchIntent(); 303c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if (appIntent != null) getContext().startActivity(appIntent); 304effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } catch (ActivityNotFoundException e) { 305a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch Log.e(TAG, "Failed to find app package: " + mAppData.packageName()); 306effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 307effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 308effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch dismiss(AppBannerMetricsIds.DISMISS_APP_OPEN); 309effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 310effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } else if (view == mCloseButtonView) { 311effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mObserver != null) { 312effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mObserver.onBannerBlocked(this, mAppData.siteUrl(), mAppData.packageName()); 313effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 314effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 315effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch dismiss(AppBannerMetricsIds.DISMISS_CLOSE_BUTTON); 316effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 317effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 318effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 319effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch @Override 320effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch protected void onViewSwipedAway() { 321effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mObserver == null) return; 322effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mObserver.onBannerDismissEvent(this, AppBannerMetricsIds.DISMISS_BANNER_SWIPE); 323effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mObserver.onBannerBlocked(this, mAppData.siteUrl(), mAppData.packageName()); 324a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 325a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 326a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @Override 327a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) protected void onViewClicked() { 328effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Send the user to the app's Play store page. 329effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch try { 330effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch IntentSender sender = mAppData.detailsIntent().getIntentSender(); 331effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch getContext().startIntentSender(sender, new Intent(), 0, 0, 0); 332effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } catch (IntentSender.SendIntentException e) { 333effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch Log.e(TAG, "Failed to launch details intent."); 334effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 335effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 336effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch dismiss(AppBannerMetricsIds.DISMISS_BANNER_CLICK); 337a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 338a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 339a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @Override 340a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) protected void onViewPressed(MotionEvent event) { 341a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Highlight the banner when the user has held it for long enough and doesn't move. 342a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mInitialXForHighlight = event.getRawX(); 343a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mIsBannerPressed = true; 344a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBannerHighlightView.setVisibility(View.VISIBLE); 345a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 346a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 347a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @Override 348effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch public void onIntentCompleted(WindowAndroid window, int resultCode, 349effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch ContentResolver contentResolver, Intent data) { 350effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (isDismissed()) return; 351effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 352effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch createVerticalSnapAnimation(true); 353effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (resultCode == Activity.RESULT_OK) { 354effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // The user chose to install the app. Watch the PackageManager to see when it finishes 355effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // installing it. 356effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mObserver.onBannerInstallEvent(this, AppBannerMetricsIds.INSTALL_STARTED); 357effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 358effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch PackageManager pm = getContext().getPackageManager(); 359effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallTask = 360effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch new InstallerDelegate(Looper.getMainLooper(), pm, this, mAppData.packageName()); 361effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallTask.start(); 362effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallState = INSTALL_STATE_INSTALLING; 363effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 364a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch updateButtonStatus(); 365effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 366effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 367effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 368effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch @Override 369effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch public void onInstallFinished(InstallerDelegate monitor, boolean success) { 370effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (isDismissed() || mInstallTask != monitor) return; 371effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 372effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (success) { 373effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Let the user open the app from here. 374effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mObserver.onBannerInstallEvent(this, AppBannerMetricsIds.INSTALL_COMPLETED); 375effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallState = INSTALL_STATE_INSTALLED; 376a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch updateButtonStatus(); 377effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } else { 378effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch dismiss(AppBannerMetricsIds.DISMISS_INSTALL_TIMEOUT); 379effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 380effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 381effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 382effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch @Override 383a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) protected ViewGroup.MarginLayoutParams createLayoutParams() { 384a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Define the margin around the entire banner that accounts for the drop shadow. 385a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ViewGroup.MarginLayoutParams params = super.createLayoutParams(); 386a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) params.setMargins(mMarginLeft, 0, mMarginRight, mMarginBottom); 387a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return params; 3885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 3895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 3905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 3915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Removes this View from its parent and alerts any observers of the dismissal. 3925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * @return Whether or not the View was successfully dismissed. 3935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 3945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 3955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) boolean removeFromParent() { 396effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (super.removeFromParent()) { 397effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mObserver.onBannerRemoved(this); 398effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch destroy(); 399effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return true; 400effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 401effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 402effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return false; 4035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 4045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 4055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 406effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Dismisses the banner. 407effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * @param eventType Event that triggered the dismissal. See {@link AppBannerMetricsIds}. 4085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 409effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch public void dismiss(int eventType) { 410effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (isDismissed() || mObserver == null) return; 411effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 412effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch dismiss(eventType == AppBannerMetricsIds.DISMISS_CLOSE_BUTTON); 413effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mObserver.onBannerDismissEvent(this, eventType); 414effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 415effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 416effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch /** 417effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * Destroys the Banner. 418effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch */ 419effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch public void destroy() { 420effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (!isDismissed()) dismiss(AppBannerMetricsIds.DISMISS_ERROR); 421effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 422effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mInstallTask != null) { 423effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallTask.cancel(); 424effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallTask = null; 425effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 4265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 4275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 4285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 429a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch * Updates the install button (install state, text, color, etc.). 4305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 431a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch void updateButtonStatus() { 432effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mInstallButtonView == null) return; 433a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 434a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch // Determine if the saved install status of the app is out of date. 435a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch // It is not easily possible to detect if an app is in the process of being installed, so we 436a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch // can't properly transition to that state from here. 437a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch if (getAppLaunchIntent() == null) { 438a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch if (mInstallState == INSTALL_STATE_INSTALLED) { 439a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch mInstallState = INSTALL_STATE_NOT_INSTALLED; 440a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch } 441a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch } else { 442a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch mInstallState = INSTALL_STATE_INSTALLED; 443a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch } 444a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 445a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch // Update what the button looks like. 446a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) Resources res = getResources(); 447a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int fgColor; 448a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) String text; 449effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mInstallState == INSTALL_STATE_INSTALLED) { 450effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch ApiCompatibilityUtils.setBackgroundForView(mInstallButtonView, 451a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) res.getDrawable(R.drawable.app_banner_button_open)); 452a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) fgColor = res.getColor(R.color.app_banner_open_button_fg); 453a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) text = res.getString(R.string.app_banner_open); 454a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } else { 455effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch ApiCompatibilityUtils.setBackgroundForView(mInstallButtonView, 456a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) res.getDrawable(R.drawable.app_banner_button_install)); 457a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) fgColor = res.getColor(R.color.app_banner_install_button_fg); 458effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mInstallState == INSTALL_STATE_NOT_INSTALLED) { 459a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) text = mAppData.installButtonText(); 460c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mInstallButtonView.setContentDescription( 461c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch getContext().getString(R.string.app_banner_install_accessibility, text)); 462a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } else { 463a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) text = res.getString(R.string.app_banner_installing); 464a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 465a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 466a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 467effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallButtonView.setTextColor(fgColor); 468effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallButtonView.setText(text); 469effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallButtonView.setEnabled(mInstallState != INSTALL_STATE_INSTALLING); 4705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 4715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 4725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 4735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Determine how big an icon needs to be for the Layout. 4745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * @param context Context to grab resources from. 4755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * @return How big the icon is expected to be, in pixels. 4765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 4775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) static int getIconSize(Context context) { 4785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return context.getResources().getDimensionPixelSize(R.dimen.app_banner_icon_size); 4795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 4805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 4815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 482a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Passes all touch events through to the parent. 483a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 484a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @Override 485a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) public boolean onTouchEvent(MotionEvent event) { 486a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int action = event.getActionMasked(); 487a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (mIsBannerPressed) { 488a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Mimic Google Now card behavior, where the card stops being highlighted if the user 489a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // scrolls a bit to the side. 490a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) float xDifference = Math.abs(event.getRawX() - mInitialXForHighlight); 491a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL 492effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch || (action == MotionEvent.ACTION_MOVE && xDifference > mTouchSlop)) { 493a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mIsBannerPressed = false; 494a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBannerHighlightView.setVisibility(View.INVISIBLE); 495a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 496a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 497a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 498a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return super.onTouchEvent(event); 499a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 500effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 501a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 5025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Fade the banner back into view. 5035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 5045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 5055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) protected void onAttachedToWindow() { 5065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) super.onAttachedToWindow(); 5075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ObjectAnimator.ofFloat(this, "alpha", getAlpha(), 1.f).setDuration( 5085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) MS_ANIMATION_DURATION).start(); 5095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) setVisibility(VISIBLE); 5105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 5115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 5125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 5135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Immediately hide the banner to avoid having them show up in snapshots. 5145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 5155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 5165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) protected void onDetachedFromWindow() { 5175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) super.onDetachedFromWindow(); 518effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch setAlpha(0.0f); 5195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) setVisibility(INVISIBLE); 5205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 5215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 5225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 523a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Watch for changes in the available screen height, which triggers a complete recreation of the 524a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * banner widgets. This is mainly due to the fact that the Nexus 7 has a smaller banner defined 525a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * for its landscape versus its portrait layouts. 526a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 527a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @Override 528a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) protected void onConfigurationChanged(Configuration config) { 529a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) super.onConfigurationChanged(config); 530a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 531effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (isDismissed()) return; 532effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 533a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // If the card's maximum width hasn't changed, the individual views can't have, either. 534a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int newDefinedWidth = getResources().getDimensionPixelSize(R.dimen.app_banner_max_width); 535a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (mDefinedMaxWidth == newDefinedWidth) return; 536a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 537a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Cannibalize another version of this layout to get Views using the new resources and 538a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // sizes. 539a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) while (getChildCount() > 0) removeViewAt(0); 540a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mIconView = null; 541a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mTitleView = null; 542effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallButtonView = null; 543a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mRatingView = null; 544a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mLogoView = null; 545a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBannerHighlightView = null; 546a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 547a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) AppBannerView cannibalized = 548a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) (AppBannerView) LayoutInflater.from(getContext()).inflate(BANNER_LAYOUT, null); 549a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) while (cannibalized.getChildCount() > 0) { 550a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) View child = cannibalized.getChildAt(0); 551a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) cannibalized.removeViewAt(0); 552a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) addView(child); 553a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 554a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) initializeControls(); 555a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) requestLayout(); 556a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch } 557a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 558a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch @Override 559a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch public void onWindowFocusChanged(boolean hasWindowFocus) { 560a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch if (hasWindowFocus) updateButtonStatus(); 561a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch } 562a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 563a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch /** 564a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch * @return Intent to launch the app that is being promoted. 565a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch */ 566a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch private Intent getAppLaunchIntent() { 567a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch String packageName = mAppData.packageName(); 568a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch PackageManager packageManager = getContext().getPackageManager(); 569a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch return packageManager.getLaunchIntentForPackage(packageName); 570a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 571a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 572a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 573a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Measures the banner and its children Views for the given space. 5745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * 575a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD 576a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * DPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPD 577a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * DP...... cPD 578effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * DP...... TITLE----------------------- XcPD 579a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * DP.ICON. ***** cPD 580a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * DP...... LOGO BUTTONcPD 581a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * DP...... cccccccccccccccccccccccccccccccPD 582a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * DPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPD 583a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD 5845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * 585a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * The three paddings mentioned in the class Javadoc are denoted by: 586a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * D) Drop shadow padding. 587a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * P) Inner card padding. 588a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * c) Control padding. 589a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 590a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Measurement for components of the banner are performed assuming that components are laid out 591a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * inside of the banner's background as follows: 592a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 1) A maximum width is enforced on the banner to keep the whole thing on screen and keep it a 593a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * reasonable size. 594a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 2) The icon takes up the left side of the banner. 595a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 3) The install button occupies the bottom-right of the banner. 596a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 4) The Google Play logo occupies the space to the left of the button. 597a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * 5) The rating is assigned space above the logo and below the title. 598effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * 6) The close button (if visible) sits in the top right of the banner. 599effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * 7) The title is assigned whatever space is left and sits on top of the tallest stack of 600a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * controls. 6015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * 6025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * See {@link #android.view.View.onMeasure(int, int)} for the parameters. 6035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 6045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 6055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 606a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Enforce a maximum width on the banner, which is defined as the smallest of: 607a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // 1) The smallest width for the device (in either landscape or portrait mode). 608a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // 2) The defined maximum width in the dimens.xml files. 609a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // 3) The width passed in through the MeasureSpec. 6105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) Resources res = getResources(); 6115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) float density = res.getDisplayMetrics().density; 6125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int screenSmallestWidth = (int) (res.getConfiguration().smallestScreenWidthDp * density); 6135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int specWidth = MeasureSpec.getSize(widthMeasureSpec); 614a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int bannerWidth = Math.min(Math.min(specWidth, mDefinedMaxWidth), screenSmallestWidth); 615a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 616a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Track how much space is available inside the banner's card-shaped background Drawable. 617a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // To calculate this, we need to account for both the padding of the background (which 618a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // is occupied by the card's drop shadows) as well as the padding defined on the inside of 619a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // the card. 620a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int bgPaddingWidth = mBackgroundDrawablePadding.left + mBackgroundDrawablePadding.right; 621a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int bgPaddingHeight = mBackgroundDrawablePadding.top + mBackgroundDrawablePadding.bottom; 622a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int maxControlWidth = bannerWidth - bgPaddingWidth - (mPaddingCard * 2); 623a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 624a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Control height is constrained to provide a reasonable aspect ratio. 625a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // In practice, the only controls which can cause an issue are the title and the install 626a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // button, since they have strings that can change size according to user preference. The 627a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // other controls are all defined to be a certain height. 628a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int specHeight = MeasureSpec.getSize(heightMeasureSpec); 629a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int reasonableHeight = maxControlWidth / 4; 630a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int paddingHeight = bgPaddingHeight + (mPaddingCard * 2); 631a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int maxControlHeight = Math.min(specHeight, reasonableHeight) - paddingHeight; 632a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int maxStackedControlHeight = maxControlWidth / 3; 633a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 634a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Determine how big each component wants to be. The icon is measured separately because 635a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // it is not stacked with the other controls. 636a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) measureChildForSpace(mIconView, maxControlWidth, maxControlHeight); 637a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for (int i = 0; i < getChildCount(); i++) { 638a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (getChildAt(i) != mIconView) { 639a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) measureChildForSpace(getChildAt(i), maxControlWidth, maxStackedControlHeight); 640a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 641a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 6425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 643a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Determine how tall the banner needs to be to fit everything by calculating the combined 644a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // height of the stacked controls. There are three competing stacks to measure: 645a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // 1) The icon. 646a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // 2) The app title + control padding + star rating + store logo. 647a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // 3) The app title + control padding + install button. 648a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // The control padding is extra padding that applies only to the non-icon widgets. 649effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // The close button does not get counted as part of a stack. 650a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int iconStackHeight = getHeightWithMargins(mIconView); 651a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int logoStackHeight = getHeightWithMargins(mTitleView) + mPaddingControls 652a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) + getHeightWithMargins(mRatingView) + getHeightWithMargins(mLogoView); 653a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int buttonStackHeight = getHeightWithMargins(mTitleView) + mPaddingControls 654effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch + getHeightWithMargins(mInstallButtonView); 655a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int biggestStackHeight = 656a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) Math.max(iconStackHeight, Math.max(logoStackHeight, buttonStackHeight)); 657a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 658a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // The icon hugs the banner's starting edge, from the top of the banner to the bottom. 659a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int iconSize = biggestStackHeight; 660a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) measureChildForSpaceExactly(mIconView, iconSize, iconSize); 661a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 662a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // The rest of the content is laid out to the right of the icon. 663a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Additional padding is defined for non-icon content on the end and bottom. 664a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int contentWidth = 665a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) maxControlWidth - getWidthWithMargins(mIconView) - mPaddingControls; 666a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int contentHeight = biggestStackHeight - mPaddingControls; 667a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) measureChildForSpace(mLogoView, contentWidth, contentHeight); 6685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 669c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch // Restrict the button size to prevent overrunning the Google Play logo. 670c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch int remainingButtonWidth = 671c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch maxControlWidth - getWidthWithMargins(mLogoView) - getWidthWithMargins(mIconView); 672c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mInstallButtonView.setMaxWidth(remainingButtonWidth); 673c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch measureChildForSpace(mInstallButtonView, contentWidth, contentHeight); 674c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 6755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Measure the star rating, which sits below the title and above the logo. 676a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int ratingWidth = contentWidth; 677a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int ratingHeight = contentHeight - getHeightWithMargins(mLogoView); 678a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) measureChildForSpace(mRatingView, ratingWidth, ratingHeight); 679a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 680effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // The close button sits to the right of the title and above the install button. 681effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch final int closeWidth = contentWidth; 682effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch final int closeHeight = contentHeight - getHeightWithMargins(mInstallButtonView); 683effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch measureChildForSpace(mCloseButtonView, closeWidth, closeHeight); 684effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 685effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // The app title spans the top of the banner and sits on top of the other controls, and to 686effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // the left of the close button. The computation for the width available to the title is 687effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // complicated by how the button sits in the corner and absorbs the padding that would 688effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // normally be there. 689effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch int biggerStack = Math.max(getHeightWithMargins(mInstallButtonView), 690a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) getHeightWithMargins(mLogoView) + getHeightWithMargins(mRatingView)); 691effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch final int titleWidth = contentWidth - getWidthWithMargins(mCloseButtonView) + mPaddingCard; 692a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int titleHeight = contentHeight - biggerStack; 693a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) measureChildForSpace(mTitleView, titleWidth, titleHeight); 694a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 695a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Set the measured dimensions for the banner. The banner's height is defined by the 696a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // tallest stack of components, the padding of the banner's card background, and the extra 697a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // padding around the banner's components. 698a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int bannerPadding = mBackgroundDrawablePadding.top + mBackgroundDrawablePadding.bottom 699a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) + (mPaddingCard * 2); 700a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int bannerHeight = biggestStackHeight + bannerPadding; 701a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) setMeasuredDimension(bannerWidth, bannerHeight); 702a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 703a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Make the banner highlight view be the exact same size as the banner's card background. 704a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int cardWidth = bannerWidth - bgPaddingWidth; 705a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) final int cardHeight = bannerHeight - bgPaddingHeight; 706a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) measureChildForSpaceExactly(mBannerHighlightView, cardWidth, cardHeight); 7075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 7085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 7095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 7105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Lays out the controls according to the algorithm in {@link #onMeasure}. 7115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * See {@link #android.view.View.onLayout(boolean, int, int, int, int)} for the parameters. 7125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 7135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) @Override 7145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) protected void onLayout(boolean changed, int l, int t, int r, int b) { 715a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) super.onLayout(changed, l, t, r, b); 716a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int top = mBackgroundDrawablePadding.top; 717a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int bottom = getMeasuredHeight() - mBackgroundDrawablePadding.bottom; 718a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int start = mBackgroundDrawablePadding.left; 719a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int end = getMeasuredWidth() - mBackgroundDrawablePadding.right; 720a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 721a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // The highlight overlay covers the entire banner (minus drop shadow padding). 722a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mBannerHighlightView.layout(start, top, end, bottom); 723a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 724effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // Lay out the close button in the top-right corner. Padding that would normally go to the 725effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch // card is applied to the close button so that it has a bigger touch target. 726effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if (mCloseButtonView.getVisibility() == VISIBLE) { 727effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch int closeWidth = mCloseButtonView.getMeasuredWidth(); 728effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch int closeTop = 729effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch top + ((MarginLayoutParams) mCloseButtonView.getLayoutParams()).topMargin; 730effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch int closeBottom = closeTop + mCloseButtonView.getMeasuredHeight(); 731effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch int closeRight = mIsLayoutLTR ? end : (getMeasuredWidth() - end + closeWidth); 732effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch int closeLeft = closeRight - closeWidth; 733effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mCloseButtonView.layout(closeLeft, closeTop, closeRight, closeBottom); 734effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch } 735effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 736a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Apply the padding for the rest of the widgets. 737a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) top += mPaddingCard; 738a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) bottom -= mPaddingCard; 739a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) start += mPaddingCard; 740a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) end -= mPaddingCard; 7415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 7425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Lay out the icon. 7435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int iconWidth = mIconView.getMeasuredWidth(); 7445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int iconLeft = mIsLayoutLTR ? start : (getMeasuredWidth() - start - iconWidth); 7455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mIconView.layout(iconLeft, top, iconLeft + iconWidth, top + mIconView.getMeasuredHeight()); 746a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) start += getWidthWithMargins(mIconView); 747a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 748a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Factor in the additional padding, which is only tacked onto the end and bottom. 749a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) end -= mPaddingControls; 750a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) bottom -= mPaddingControls; 7515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 752a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Lay out the app title text. 7535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int titleWidth = mTitleView.getMeasuredWidth(); 754a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int titleTop = top + ((MarginLayoutParams) mTitleView.getLayoutParams()).topMargin; 755a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int titleBottom = titleTop + mTitleView.getMeasuredHeight(); 7565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int titleLeft = mIsLayoutLTR ? start : (getMeasuredWidth() - start - titleWidth); 7575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mTitleView.layout(titleLeft, titleTop, titleLeft + titleWidth, titleBottom); 758a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 759a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // The mock shows the margin eating into the descender area of the TextView. 760a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int textBaseline = mTitleView.getLineBounds(mTitleView.getLineCount() - 1, null); 761a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) top = titleTop + textBaseline 762a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) + ((MarginLayoutParams) mTitleView.getLayoutParams()).bottomMargin; 7635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 7645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Lay out the app rating below the title. 7655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int starWidth = mRatingView.getMeasuredWidth(); 7665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int starTop = top + ((MarginLayoutParams) mRatingView.getLayoutParams()).topMargin; 7675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int starBottom = starTop + mRatingView.getMeasuredHeight(); 7685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int starLeft = mIsLayoutLTR ? start : (getMeasuredWidth() - start - starWidth); 7695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mRatingView.layout(starLeft, starTop, starLeft + starWidth, starBottom); 7705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 7715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Lay out the logo in the bottom-left. 7725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int logoWidth = mLogoView.getMeasuredWidth(); 7735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int logoBottom = bottom - ((MarginLayoutParams) mLogoView.getLayoutParams()).bottomMargin; 7745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int logoTop = logoBottom - mLogoView.getMeasuredHeight(); 7755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int logoLeft = mIsLayoutLTR ? start : (getMeasuredWidth() - start - logoWidth); 7765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) mLogoView.layout(logoLeft, logoTop, logoLeft + logoWidth, logoBottom); 7775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 7785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Lay out the install button in the bottom-right corner. 779effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch int buttonHeight = mInstallButtonView.getMeasuredHeight(); 780effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch int buttonWidth = mInstallButtonView.getMeasuredWidth(); 7815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int buttonRight = mIsLayoutLTR ? end : (getMeasuredWidth() - end + buttonWidth); 7825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int buttonLeft = buttonRight - buttonWidth; 783effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mInstallButtonView.layout(buttonLeft, bottom - buttonHeight, buttonRight, bottom); 7845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 7855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 7865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 787a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Measures a child for the given space, accounting for defined heights and margins. 788a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param child View to measure. 789a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param availableWidth Available width for the view. 790a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param availableHeight Available height for the view. 791a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 792a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private void measureChildForSpace(View child, int availableWidth, int availableHeight) { 793a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Handle margins. 794a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) availableWidth -= getMarginWidth(child); 795a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) availableHeight -= getMarginHeight(child); 796a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 797a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) // Account for any layout-defined dimensions for the view. 798a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int childWidth = child.getLayoutParams().width; 799a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int childHeight = child.getLayoutParams().height; 800a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (childWidth >= 0) availableWidth = Math.min(availableWidth, childWidth); 801a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (childHeight >= 0) availableHeight = Math.min(availableHeight, childHeight); 802a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 803a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST); 804a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int heightSpec = MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST); 805a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) child.measure(widthSpec, heightSpec); 806a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 807a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 808a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 809a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Forces a child to exactly occupy the given space. 810a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param child View to measure. 811a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param availableWidth Available width for the view. 812a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param availableHeight Available height for the view. 813a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 814a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private void measureChildForSpaceExactly(View child, int availableWidth, int availableHeight) { 815a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY); 816a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) int heightSpec = MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY); 817a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) child.measure(widthSpec, heightSpec); 818a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 819a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 820a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 821a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Calculates how wide the margins are for the given View. 822a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param view View to measure. 823a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @return Measured width of the margins. 824a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */ 825a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private static int getMarginWidth(View view) { 826a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams(); 827a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return params.leftMargin + params.rightMargin; 828a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 829a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 830a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) /** 8315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Calculates how wide the given View has been measured to be, including its margins. 832a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param view View to measure. 833a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @return Measured width of the view plus its margins. 8345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 835a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private static int getWidthWithMargins(View view) { 836a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return view.getMeasuredWidth() + getMarginWidth(view); 8375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 840a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Calculates how tall the margins are for the given View. 841a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param view View to measure. 842a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @return Measured height of the margins. 8435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 844a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private static int getMarginHeight(View view) { 845a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams(); 846a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return params.topMargin + params.bottomMargin; 8475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 8495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 850a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * Calculates how tall the given View has been measured to be, including its margins. 851a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @param view View to measure. 852a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @return Measured height of the view plus its margins. 8535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 854a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private static int getHeightWithMargins(View view) { 855a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return view.getMeasuredHeight() + getMarginHeight(view); 8565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 8575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 858