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