1d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/* 2d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Copyright (C) 2015 The Android Open Source Project 3d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 4d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Licensed under the Apache License, Version 2.0 (the "License"); 5d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * you may not use this file except in compliance with the License. 6d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * You may obtain a copy of the License at 7d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 8d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * http://www.apache.org/licenses/LICENSE-2.0 9d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 10d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Unless required by applicable law or agreed to in writing, software 11d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * distributed under the License is distributed on an "AS IS" BASIS, 12d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * See the License for the specific language governing permissions and 14d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * limitations under the License. 15d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 16d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 17d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpackage com.android.messaging.ui; 18d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 19d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.content.Context; 20d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.graphics.Rect; 21d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.util.AttributeSet; 22d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.LayoutInflater; 23d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.View; 24d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.animation.AnimationSet; 25d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.animation.ScaleAnimation; 26d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.animation.TranslateAnimation; 27d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.widget.FrameLayout; 28d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.widget.TextView; 29d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 30d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.R; 31d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.data.MediaPickerMessagePartData; 32d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.data.MessagePartData; 33d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.data.PendingAttachmentData; 34d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.media.ImageRequestDescriptor; 35d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader; 36d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.AccessibilityUtil; 37d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.Assert; 38d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.UiUtils; 39d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 40d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.ArrayList; 41d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.Arrays; 42d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.Iterator; 43d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.List; 44d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 45d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/** 46d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Holds and displays multiple attachments in a 4x2 grid. Each preview image "tile" can take 47d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * one of three sizes - small (1x1), wide (2x1) and large (2x2). We have a number of predefined 48d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * layout settings designed for holding 2, 3, 4+ attachments (these layout settings are 49d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * tweakable by design request to allow for max flexibility). For a visual example, consider the 50d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * following attachment layout: 51d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 52d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * +---------------+----------------+ 53d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * | | | 54d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * | | B | 55d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * | | | 56d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * | A |-------+--------| 57d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * | | | | 58d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * | | C | D | 59d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * | | | | 60d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * +---------------+-------+--------+ 61d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 62d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * In the above example, the layout consists of four tiles, A-D. A is a large tile, B is a 63d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * wide tile and C & D are both small tiles. A starts at (0,0) and ends at (1,1), B starts at 64d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * (2,0) and ends at (3,0), and so on. In our layout class we'd have these tiles in the order 65d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * of A-D, so that we make sure the last tile is always the one where we can put the overflow 66d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * indicator (e.g. "+2"). 67d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 68d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpublic class MultiAttachmentLayout extends FrameLayout { 69d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 70d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public interface OnAttachmentClickListener { 71d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd boolean onAttachmentClick(MessagePartData attachment, Rect viewBoundsOnScreen, 72d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd boolean longPress); 73d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 74d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 75d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int GRID_WIDTH = 4; // in # of cells 76d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int GRID_HEIGHT = 2; // in # of cells 77d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 78d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 79d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Represents a preview image tile in the layout 80d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 81d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static class Tile { 82d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public final int startX; 83d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public final int startY; 84d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public final int endX; 85d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public final int endY; 86d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 87d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private Tile(final int startX, final int startY, final int endX, final int endY) { 88d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd this.startX = startX; 89d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd this.startY = startY; 90d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd this.endX = endX; 91d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd this.endY = endY; 92d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 93d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 94d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public int getWidthMeasureSpec(final int cellWidth, final int padding) { 95d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return MeasureSpec.makeMeasureSpec((endX - startX + 1) * cellWidth - padding * 2, 96d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd MeasureSpec.EXACTLY); 97d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 98d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 99d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public int getHeightMeasureSpec(final int cellHeight, final int padding) { 100d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return MeasureSpec.makeMeasureSpec((endY - startY + 1) * cellHeight - padding * 2, 101d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd MeasureSpec.EXACTLY); 102d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 103d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 104d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static Tile large(final int startX, final int startY) { 105d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return new Tile(startX, startY, startX + 1, startY + 1); 106d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 107d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 108d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static Tile wide(final int startX, final int startY) { 109d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return new Tile(startX, startY, startX + 1, startY); 110d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 111d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 112d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static Tile small(final int startX, final int startY) { 113d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return new Tile(startX, startY, startX, startY); 114d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 115d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 116d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 117d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 118d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * A layout simply contains a list of tiles, in the order of top-left -> bottom-right. 119d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 120d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static class Layout { 121d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public final List<Tile> tiles; 122d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public Layout(final Tile[] tilesArray) { 123d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd tiles = Arrays.asList(tilesArray); 124d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 125d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 126d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 127d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 128d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * List of predefined layout configurations w.r.t no. of attachments. 129d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 130d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final Layout[] ATTACHMENT_LAYOUTS_BY_COUNT = { 131d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd null, // Doesn't support zero attachments. 132d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd null, // Doesn't support one attachment. Single attachment preview is used instead. 133d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd new Layout(new Tile[] { Tile.large(0, 0), Tile.large(2, 0) }), // 2 items 134d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.wide(2, 1) }), // 3 items 135d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.small(2, 1), // 4+ items 136d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Tile.small(3, 1) }), 137d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd }; 138d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 139d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 140d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * List of predefined RTL layout configurations w.r.t no. of attachments. 141d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 142d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final Layout[] ATTACHMENT_RTL_LAYOUTS_BY_COUNT = { 143d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd null, // Doesn't support zero attachments. 144d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd null, // Doesn't support one attachment. Single attachment preview is used instead. 145d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd new Layout(new Tile[] { Tile.large(2, 0), Tile.large(0, 0)}), // 2 items 146d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.wide(0, 1) }), // 3 items 147d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.small(1, 1), // 4+ items 148d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Tile.small(0, 1) }), 149d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd }; 150d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 151d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private Layout mCurrentLayout; 152d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private ArrayList<ViewWrapper> mPreviewViews; 153d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private int mPlusNumber; 154d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private TextView mPlusTextView; 155d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private OnAttachmentClickListener mAttachmentClickListener; 156d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private AsyncImageViewDelayLoader mImageViewDelayLoader; 157d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 158d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public MultiAttachmentLayout(final Context context, final AttributeSet attrs) { 159d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd super(context, attrs); 160d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewViews = new ArrayList<ViewWrapper>(); 161d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 162d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 163d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void bindAttachments(final Iterable<MessagePartData> attachments, 164d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final Rect transitionRect, final int count) { 165d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ArrayList<ViewWrapper> previousViews = mPreviewViews; 166d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewViews = new ArrayList<ViewWrapper>(); 167d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd removeView(mPlusTextView); 168d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPlusTextView = null; 169d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 170d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd determineLayout(attachments, count); 171d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd buildViews(attachments, previousViews, transitionRect); 172d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 173d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Remove all previous views that couldn't be recycled. 174d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd for (final ViewWrapper viewWrapper : previousViews) { 175d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd removeView(viewWrapper.view); 176d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 177d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd requestLayout(); 178d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 179d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 180d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public OnAttachmentClickListener getOnAttachmentClickListener() { 181d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return mAttachmentClickListener; 182d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 183d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 184d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void setOnAttachmentClickListener(final OnAttachmentClickListener listener) { 185d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mAttachmentClickListener = listener; 186d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 187d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 188d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) { 189d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mImageViewDelayLoader = delayLoader; 190d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 191d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 192d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void setColorFilter(int color) { 193d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd for (ViewWrapper viewWrapper : mPreviewViews) { 194d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (viewWrapper.view instanceof AsyncImageView) { 195d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ((AsyncImageView) viewWrapper.view).setColorFilter(color); 196d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 197d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 198d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 199d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 200d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void clearColorFilter() { 201d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd for (ViewWrapper viewWrapper : mPreviewViews) { 202d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (viewWrapper.view instanceof AsyncImageView) { 203d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ((AsyncImageView) viewWrapper.view).clearColorFilter(); 204d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 205d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 206d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 207d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 208d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void determineLayout(final Iterable<MessagePartData> attachments, final int count) { 209d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.isTrue(attachments != null); 210d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final boolean isRtl = AccessibilityUtil.isLayoutRtl(getRootView()); 211d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (isRtl) { 212d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mCurrentLayout = ATTACHMENT_RTL_LAYOUTS_BY_COUNT[Math.min(count, 213d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ATTACHMENT_RTL_LAYOUTS_BY_COUNT.length - 1)]; 214d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 215d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mCurrentLayout = ATTACHMENT_LAYOUTS_BY_COUNT[Math.min(count, 216d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ATTACHMENT_LAYOUTS_BY_COUNT.length - 1)]; 217d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 218d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 219d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // We must have a valid layout for the current configuration. 220d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.notNull(mCurrentLayout); 221d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 222d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPlusNumber = count - mCurrentLayout.tiles.size(); 223d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.isTrue(mPlusNumber >= 0); 224d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 225d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 226d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void buildViews(final Iterable<MessagePartData> attachments, 227d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ArrayList<ViewWrapper> previousViews, final Rect transitionRect) { 228d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final LayoutInflater layoutInflater = LayoutInflater.from(getContext()); 229d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int count = mCurrentLayout.tiles.size(); 230d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int i = 0; 231d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final Iterator<MessagePartData> iterator = attachments.iterator(); 232d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd while (iterator.hasNext() && i < count) { 233d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final MessagePartData attachment = iterator.next(); 234d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ViewWrapper attachmentWrapper = null; 235d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Try to recycle a previous view first 236d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd for (int j = 0; j < previousViews.size(); j++) { 237d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ViewWrapper previousView = previousViews.get(j); 238d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (previousView.attachment.equals(attachment) && 239d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd !(previousView.attachment instanceof PendingAttachmentData)) { 240d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd attachmentWrapper = previousView; 241d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd previousViews.remove(j); 242d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd break; 243d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 244d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 245d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 246d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (attachmentWrapper == null) { 247d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final View view = AttachmentPreviewFactory.createAttachmentPreview(layoutInflater, 248d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd attachment, this, AttachmentPreviewFactory.TYPE_MULTIPLE, 249d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd false /* startImageRequest */, mAttachmentClickListener); 250d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 251d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (view == null) { 252d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // createAttachmentPreview can return null if something goes wrong (e.g. 253d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // attachment has unsupported contentType) 254d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd continue; 255d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 256d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (view instanceof AsyncImageView && mImageViewDelayLoader != null) { 257d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd AsyncImageView asyncImageView = (AsyncImageView) view; 258d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd asyncImageView.setDelayLoader(mImageViewDelayLoader); 259d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 260d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd addView(view); 261d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd attachmentWrapper = new ViewWrapper(view, attachment); 262d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Help animate from single to multi by copying over the prev location 263d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (count == 2 && i == 1 && transitionRect != null) { 264d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd attachmentWrapper.prevLeft = transitionRect.left; 265d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd attachmentWrapper.prevTop = transitionRect.top; 266d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd attachmentWrapper.prevWidth = transitionRect.width(); 267d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd attachmentWrapper.prevHeight = transitionRect.height(); 268d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 269d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 270d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd i++; 271d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.notNull(attachmentWrapper); 272d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewViews.add(attachmentWrapper); 273d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 274d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // The first view will animate in using PopupTransitionAnimation, but the remaining 275d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // views will slide from their previous position to their new position within the 276d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // layout 277d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (i == 0) { 278d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd AttachmentPreview.tryAnimateViewIn(attachment, attachmentWrapper.view); 279d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 280d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd attachmentWrapper.needsSlideAnimation = i > 0; 281d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 282d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 283d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Build the plus text view (e.g. "+2") for when there are more attachments than what 284d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // this layout can display. 285d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mPlusNumber > 0) { 286d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPlusTextView = (TextView) layoutInflater.inflate(R.layout.attachment_more_text_view, 287d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd null /* parent */); 288d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPlusTextView.setText(getResources().getString(R.string.attachment_more_items, 289d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPlusNumber)); 290d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd addView(mPlusTextView); 291d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 292d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 293d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 294d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 295d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { 296d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int maxWidth = getResources().getDimensionPixelSize( 297d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd R.dimen.multiple_attachment_preview_width); 298d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int maxHeight = getResources().getDimensionPixelSize( 299d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd R.dimen.multiple_attachment_preview_height); 300d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int width = Math.min(MeasureSpec.getSize(widthMeasureSpec), maxWidth); 301d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int height = maxHeight; 302d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int cellWidth = width / GRID_WIDTH; 303d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int cellHeight = height / GRID_HEIGHT; 304d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int count = mPreviewViews.size(); 305d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int padding = getResources().getDimensionPixelOffset( 306d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd R.dimen.multiple_attachment_preview_padding); 307d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd for (int i = 0; i < count; i++) { 308d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final View view = mPreviewViews.get(i).view; 309d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final Tile imageTile = mCurrentLayout.tiles.get(i); 310d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd view.measure(imageTile.getWidthMeasureSpec(cellWidth, padding), 311d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageTile.getHeightMeasureSpec(cellHeight, padding)); 312d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 313d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Now that we know the size, we can request an appropriately-sized image. 314d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (view instanceof AsyncImageView) { 315d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ImageRequestDescriptor imageRequest = 316d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd AttachmentPreviewFactory.getImageRequestDescriptorForAttachment( 317d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewViews.get(i).attachment, 318d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd view.getMeasuredWidth(), 319d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd view.getMeasuredHeight()); 320d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ((AsyncImageView) view).setImageResourceId(imageRequest); 321d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 322d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 323d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (i == count - 1 && mPlusTextView != null) { 324d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // The plus text view always covers the last attachment. 325d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPlusTextView.measure(imageTile.getWidthMeasureSpec(cellWidth, padding), 326d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageTile.getHeightMeasureSpec(cellHeight, padding)); 327d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 328d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 329d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd setMeasuredDimension(width, height); 330d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 331d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 332d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 333d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd protected void onLayout(final boolean changed, final int left, final int top, final int right, 334d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int bottom) { 335d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int cellWidth = getMeasuredWidth() / GRID_WIDTH; 336d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int cellHeight = getMeasuredHeight() / GRID_HEIGHT; 337d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int padding = getResources().getDimensionPixelOffset( 338d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd R.dimen.multiple_attachment_preview_padding); 339d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int count = mPreviewViews.size(); 340d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd for (int i = 0; i < count; i++) { 341d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ViewWrapper viewWrapper = mPreviewViews.get(i); 342d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final View view = viewWrapper.view; 343d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final Tile imageTile = mCurrentLayout.tiles.get(i); 344d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int tileLeft = imageTile.startX * cellWidth; 345d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int tileTop = imageTile.startY * cellHeight; 346d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd view.layout(tileLeft + padding, tileTop + padding, 347d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd tileLeft + view.getMeasuredWidth(), 348d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd tileTop + view.getMeasuredHeight()); 349d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (viewWrapper.needsSlideAnimation) { 350d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd trySlideAttachmentView(viewWrapper); 351d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd viewWrapper.needsSlideAnimation = false; 352d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 353d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd viewWrapper.prevLeft = view.getLeft(); 354d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd viewWrapper.prevTop = view.getTop(); 355d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd viewWrapper.prevWidth = view.getWidth(); 356d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd viewWrapper.prevHeight = view.getHeight(); 357d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 358d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 359d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (i == count - 1 && mPlusTextView != null) { 360d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // The plus text view always covers the last attachment. 361d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPlusTextView.layout(tileLeft + padding, tileTop + padding, 362d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd tileLeft + mPlusTextView.getMeasuredWidth(), 363d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd tileTop + mPlusTextView.getMeasuredHeight()); 364d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 365d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 366d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 367d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 368d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void trySlideAttachmentView(final ViewWrapper viewWrapper) { 369d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (!(viewWrapper.attachment instanceof MediaPickerMessagePartData)) { 370d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return; 371d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 372d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final View view = viewWrapper.view; 373d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 374d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 375d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int xOffset = viewWrapper.prevLeft - view.getLeft(); 376d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int yOffset = viewWrapper.prevTop - view.getTop(); 377d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final float scaleX = viewWrapper.prevWidth / (float) view.getWidth(); 378d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final float scaleY = viewWrapper.prevHeight / (float) view.getHeight(); 379d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 380d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (xOffset == 0 && yOffset == 0 && scaleX == 1 && scaleY == 1) { 381d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Layout hasn't changed 382d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return; 383d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 384d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 385d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final AnimationSet animationSet = new AnimationSet( 386d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd true /* shareInterpolator */); 387d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd animationSet.addAnimation(new TranslateAnimation(xOffset, 0, yOffset, 0)); 388d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd animationSet.addAnimation(new ScaleAnimation(scaleX, 1, scaleY, 1)); 389d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd animationSet.setDuration( 390d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd UiUtils.MEDIAPICKER_TRANSITION_DURATION); 391d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd animationSet.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR); 392d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd view.startAnimation(animationSet); 393d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd view.invalidate(); 394d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd viewWrapper.prevLeft = view.getLeft(); 395d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd viewWrapper.prevTop = view.getTop(); 396d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd viewWrapper.prevWidth = view.getWidth(); 397d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd viewWrapper.prevHeight = view.getHeight(); 398d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 399d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 400d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public View findViewForAttachment(final MessagePartData attachment) { 401d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd for (ViewWrapper wrapper : mPreviewViews) { 402d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (wrapper.attachment.equals(attachment) && 403d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd !(wrapper.attachment instanceof PendingAttachmentData)) { 404d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return wrapper.view; 405d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 406d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 407d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return null; 408d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 409d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 410d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static class ViewWrapper { 411d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final View view; 412d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final MessagePartData attachment; 413d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd boolean needsSlideAnimation; 414d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int prevLeft; 415d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int prevTop; 416d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int prevWidth; 417d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int prevHeight; 418d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 419d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ViewWrapper(final View view, final MessagePartData attachment) { 420d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd this.view = view; 421d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd this.attachment = attachment; 422d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 423d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 424d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd} 425