Merge "reduce size of tiny planet if malloc fails" into gb-ub-photos-arches
diff --git a/res/values-it/filtershow_strings.xml b/res/values-it/filtershow_strings.xml
index 8914450..33f6012 100644
--- a/res/values-it/filtershow_strings.xml
+++ b/res/values-it/filtershow_strings.xml
@@ -56,7 +56,7 @@
     <string name="vibrance" msgid="3326744578577835915">"Vividezza"</string>
     <string name="saturation" msgid="7026791551032438585">"Saturazione"</string>
     <string name="bwfilter" msgid="8927492494576933793">"Filtro BN"</string>
-    <string name="wbalance" msgid="6346581563387083613">"Colore automatico"</string>
+    <string name="wbalance" msgid="6346581563387083613">"Colore autom."</string>
     <string name="hue" msgid="6231252147971086030">"Tonalità"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Ombre"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Curve"</string>
diff --git a/res/values-iw/filtershow_strings.xml b/res/values-iw/filtershow_strings.xml
index f16ad3c..95942dc 100644
--- a/res/values-iw/filtershow_strings.xml
+++ b/res/values-iw/filtershow_strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="title_activity_filter_show" msgid="2036539130684382763">"עורך תמונות"</string>
-    <string name="cannot_load_image" msgid="5023634941212959976">"לא ניתן לטעון את התמונה!"</string>
+    <string name="cannot_load_image" msgid="5023634941212959976">"לא ניתן להעלות את התמונה!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
     <string name="original" msgid="3524493791230430897">"מקור"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 7ecbbc2..153a2b2 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -28,7 +28,7 @@
     <string name="resume_playing_message" msgid="5184414518126703481">"להמשיך להפעיל מ-%s?"</string>
     <string name="resume_playing_resume" msgid="3847915469173852416">"המשך את ההפעלה"</string>
     <string name="loading" msgid="7038208555304563571">"טוען..."</string>
-    <string name="fail_to_load" msgid="8394392853646664505">"לא ניתן לטעון"</string>
+    <string name="fail_to_load" msgid="8394392853646664505">"לא ניתן להעלות"</string>
     <string name="fail_to_load_image" msgid="6155388718549782425">"לא היתה אפשרות לטעון את התמונה"</string>
     <string name="no_thumbnail" msgid="284723185546429750">"ללא תמונה ממוזערת"</string>
     <string name="resume_playing_restart" msgid="5471008499835769292">"התחל מחדש"</string>
diff --git a/res/values-ru/filtershow_strings.xml b/res/values-ru/filtershow_strings.xml
index 05be3a5..215dffb 100644
--- a/res/values-ru/filtershow_strings.xml
+++ b/res/values-ru/filtershow_strings.xml
@@ -36,7 +36,7 @@
     <skip />
     <string name="imageState" msgid="3609930035023754855">"Состояние изображения"</string>
     <string name="compare_original" msgid="8140838959007796977">"Сравнить"</string>
-    <string name="apply_effect" msgid="1218288221200568947">"Применить"</string>
+    <string name="apply_effect" msgid="1218288221200568947">"Применить:"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Сброс"</string>
     <string name="aspect" msgid="4025244950820813059">"Формат"</string>
     <string name="aspect1to1_effect" msgid="1159104543795779123">"1:1"</string>
@@ -46,27 +46,27 @@
     <string name="aspect5to7_effect" msgid="5122395569059384741">"5:7"</string>
     <string name="aspect7to5_effect" msgid="5780001758108328143">"7:5"</string>
     <string name="aspect9to16_effect" msgid="7740468012919660728">"16:9"</string>
-    <string name="aspectNone_effect" msgid="6263330561046574134">"Нет"</string>
+    <string name="aspectNone_effect" msgid="6263330561046574134">"Оригинал"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
-    <string name="tinyplanet" msgid="2783694326474415761">"Круговая панорама"</string>
+    <string name="tinyplanet" msgid="2783694326474415761">"Закругление"</string>
     <string name="exposure" msgid="6526397045949374905">"Экспозиция"</string>
     <string name="sharpness" msgid="6463103068318055412">"Резкость"</string>
-    <string name="contrast" msgid="2310908487756769019">"Контрастность"</string>
+    <string name="contrast" msgid="2310908487756769019">"Контраст"</string>
     <string name="vibrance" msgid="3326744578577835915">"Вибрация"</string>
-    <string name="saturation" msgid="7026791551032438585">"Насыщенность"</string>
-    <string name="bwfilter" msgid="8927492494576933793">"Ч/Б фильтр"</string>
-    <string name="wbalance" msgid="6346581563387083613">"Автонастройка"</string>
+    <string name="saturation" msgid="7026791551032438585">"Насыщ-сть"</string>
+    <string name="bwfilter" msgid="8927492494576933793">"Ч/Б"</string>
+    <string name="wbalance" msgid="6346581563387083613">"Авторежим"</string>
     <string name="hue" msgid="6231252147971086030">"Оттенок"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Тени"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Кривые"</string>
-    <string name="vignette" msgid="934721068851885390">"Виньетирование"</string>
+    <string name="vignette" msgid="934721068851885390">"Виньет-ние"</string>
     <string name="redeye" msgid="4508883127049472069">"Красные глаза"</string>
     <string name="straighten" msgid="26025591664983528">"Выровнять"</string>
     <string name="crop" msgid="5781263790107850771">"Кадрирование"</string>
     <string name="rotate" msgid="2796802553793795371">"Повернуть"</string>
-    <string name="mirror" msgid="5482518108154883096">"Зерк. отражение"</string>
-    <string name="none" msgid="6633966646410296520">"Нет"</string>
+    <string name="mirror" msgid="5482518108154883096">"Отразить"</string>
+    <string name="none" msgid="6633966646410296520">"Оригинал"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Красный"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Зеленый"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 9c5ac01..d55f6dc 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -144,11 +144,11 @@
     <string name="ffx_original" msgid="372686331501281474">"Оригинал"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Винтаж"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Полароид"</string>
-    <string name="ffx_bleach" msgid="8946700451603478453">"Отбеливатель"</string>
-    <string name="ffx_blue_crush" msgid="6034283412305561226">"Синий"</string>
+    <string name="ffx_bleach" msgid="8946700451603478453">"Отбеливание"</string>
+    <string name="ffx_blue_crush" msgid="6034283412305561226">"Синева"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"Ч/Б"</string>
     <string name="ffx_punch" msgid="1343475517872562639">"Сжатие"</string>
-    <string name="ffx_x_process" msgid="4779398678661811765">"Перекрестная обработка"</string>
+    <string name="ffx_x_process" msgid="4779398678661811765">"X-процесс"</string>
     <string name="ffx_washout" msgid="4594160692176642735">"Латте"</string>
     <string name="ffx_washout_color" msgid="8034075742195795219">"Литография"</string>
   <plurals name="make_albums_available_offline">
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 966b005..c59e24c 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -178,7 +178,7 @@
     <string name="albums" msgid="7320787705180057947">"Album"</string>
     <string name="times" msgid="2023033894889499219">"Lần"</string>
     <string name="locations" msgid="6649297994083130305">"Vị trí"</string>
-    <string name="people" msgid="4114003823747292747">"Mọi người"</string>
+    <string name="people" msgid="4114003823747292747">"Danh bạ"</string>
     <string name="tags" msgid="5539648765482935955">"Thẻ"</string>
     <string name="group_by" msgid="4308299657902209357">"Nhóm theo"</string>
     <string name="settings" msgid="1534847740615665736">"Cài đặt"</string>
diff --git a/src/com/android/gallery3d/app/OrientationManager.java b/src/com/android/gallery3d/app/OrientationManager.java
index 0e033eb..0a644ef 100644
--- a/src/com/android/gallery3d/app/OrientationManager.java
+++ b/src/com/android/gallery3d/app/OrientationManager.java
@@ -27,30 +27,16 @@
 
 import com.android.gallery3d.ui.OrientationSource;
 
-import java.util.ArrayList;
-
 public class OrientationManager implements OrientationSource {
     private static final String TAG = "OrientationManager";
 
-    public interface Listener {
-        public void onOrientationCompensationChanged();
-    }
-
     // Orientation hysteresis amount used in rounding, in degrees
     private static final int ORIENTATION_HYSTERESIS = 5;
 
     private Activity mActivity;
-    private ArrayList<Listener> mListeners;
     private MyOrientationEventListener mOrientationListener;
-    // The degrees of the device rotated clockwise from its natural orientation.
-    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
     // If the framework orientation is locked.
     private boolean mOrientationLocked = false;
-    // The orientation compensation: if the framwork orientation is locked, the
-    // device orientation and the framework orientation may be different, so we
-    // need to rotate the UI. For example, if this value is 90, the UI
-    // components should be rotated 90 degrees counter-clockwise.
-    private int mOrientationCompensation = 0;
 
     // This is true if "Settings -> Display -> Rotation Lock" is checked. We
     // don't allow the orientation to be unlocked if the value is true.
@@ -58,7 +44,6 @@
 
     public OrientationManager(Activity activity) {
         mActivity = activity;
-        mListeners = new ArrayList<Listener>();
         mOrientationListener = new MyOrientationEventListener(activity);
     }
 
@@ -73,18 +58,6 @@
         mOrientationListener.disable();
     }
 
-    public void addListener(Listener listener) {
-        synchronized (mListeners) {
-            mListeners.add(listener);
-        }
-    }
-
-    public void removeListener(Listener listener) {
-        synchronized (mListeners) {
-            mListeners.remove(listener);
-        }
-    }
-
     ////////////////////////////////////////////////////////////////////////////
     //  Orientation handling
     //
@@ -98,15 +71,27 @@
     public void lockOrientation() {
         if (mOrientationLocked) return;
         mOrientationLocked = true;
+        mActivity.setRequestedOrientation(calculateCurrentScreenOrientation());
+    }
+
+    // Unlock the framework orientation, so it can change when the device
+    // rotates.
+    public void unlockOrientation() {
+        if (!mOrientationLocked) return;
+        mOrientationLocked = false;
+        Log.d(TAG, "unlock orientation");
+        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    private int calculateCurrentScreenOrientation() {
         int displayRotation = getDisplayRotation();
         // Display rotation >= 180 means we need to use the REVERSE landscape/portrait
         boolean standard = displayRotation < 180;
         if (mActivity.getResources().getConfiguration().orientation
                 == Configuration.ORIENTATION_LANDSCAPE) {
-            Log.d(TAG, "lock orientation to landscape");
-            mActivity.setRequestedOrientation(standard
+            return standard
                     ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
         } else {
             if (displayRotation == 90 || displayRotation == 270) {
                 // If displayRotation = 90 or 270 then we are on a landscape
@@ -115,53 +100,9 @@
                 // to flip which portrait we pick as display rotation is counter clockwise
                 standard = !standard;
             }
-            Log.d(TAG, "lock orientation to portrait");
-            mActivity.setRequestedOrientation(standard
+            return standard
                     ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
-        }
-        updateCompensation();
-    }
-
-    // Unlock the framework orientation, so it can change when the device
-    // rotates.
-    public void unlockOrientation() {
-        if (!mOrientationLocked) return;
-        if (mRotationLockedSetting) return;
-        mOrientationLocked = false;
-        Log.d(TAG, "unlock orientation");
-        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-        disableCompensation();
-    }
-
-    // Calculate the compensation value and send it to listeners.
-    private void updateCompensation() {
-        if (mOrientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
-            return;
-        }
-
-        int orientationCompensation =
-            (mOrientation + getDisplayRotation(mActivity)) % 360;
-
-        if (mOrientationCompensation != orientationCompensation) {
-            mOrientationCompensation = orientationCompensation;
-            notifyListeners();
-        }
-    }
-
-    // Make the compensation value 0 and send it to listeners.
-    private void disableCompensation() {
-        if (mOrientationCompensation != 0) {
-            mOrientationCompensation = 0;
-            notifyListeners();
-        }
-    }
-
-    private void notifyListeners() {
-        synchronized (mListeners) {
-            for (int i = 0, n = mListeners.size(); i < n; i++) {
-                mListeners.get(i).onOrientationCompensationChanged();
-            }
+                    : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
         }
     }
 
@@ -177,10 +118,7 @@
             // the camera then point the camera to floor or sky, we still have
             // the correct orientation.
             if (orientation == ORIENTATION_UNKNOWN) return;
-            mOrientation = roundOrientation(orientation, mOrientation);
-            // If the framework orientation is locked, we update the
-            // compensation value and notify the listeners.
-            if (mOrientationLocked) updateCompensation();
+            orientation = roundOrientation(orientation, 0);
         }
     }
 
@@ -191,7 +129,7 @@
 
     @Override
     public int getCompensation() {
-        return mOrientationCompensation;
+        return 0;
     }
 
     private static int roundOrientation(int orientation, int orientationHistory) {
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index cda0f34..b43cf2a 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -72,7 +72,7 @@
 import com.android.gallery3d.util.GalleryUtils;
 
 public class PhotoPage extends ActivityState implements
-        PhotoView.Listener, OrientationManager.Listener, AppBridge.Server,
+        PhotoView.Listener, AppBridge.Server,
         PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener {
     private static final String TAG = "PhotoPage";
 
@@ -285,7 +285,6 @@
         mRootPane.addComponent(mPhotoView);
         mApplication = (GalleryApp) ((Activity) mActivity).getApplication();
         mOrientationManager = mActivity.getOrientationManager();
-        mOrientationManager.addListener(this);
         mActivity.getGLRoot().setOrientationSource(mOrientationManager);
 
         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
@@ -865,11 +864,6 @@
     }
 
     @Override
-    public void onOrientationCompensationChanged() {
-        mActivity.getGLRoot().requestLayoutContentPane();
-    }
-
-    @Override
     protected void onBackPressed() {
         if (mShowDetails) {
             hideDetails();
@@ -1424,7 +1418,6 @@
             mScreenNailSet = null;
             mScreenNailItem = null;
         }
-        mOrientationManager.removeListener(this);
         mActivity.getGLRoot().setOrientationSource(null);
         if (mBottomControls != null) mBottomControls.cleanup();
 
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index ef0415f..e02a751 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -159,6 +159,8 @@
         // TODO: get those values from XML.
         ImageZoom.setZoomedSize(getPixelsFromDip(256));
         FramedTextButton.setTextSize((int) getPixelsFromDip(14));
+        FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
+        FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
         ImageShow.setTextSize((int) getPixelsFromDip(12));
         ImageShow.setTextPadding((int) getPixelsFromDip(10));
         ImageShow.setOriginalTextMargin((int) getPixelsFromDip(4));
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
index bdcb0ea..d74a6fa 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
@@ -72,7 +72,7 @@
         Rect cropBounds = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
         RectF crop = mGeometry.getCropBounds(bitmap);
         if (crop.width() > 0 && crop.height() > 0)
-            crop.roundOut(cropBounds);
+            cropBounds = GeometryMath.roundNearest(crop);
         Bitmap temp = null;
         if (mGeometry.hasSwitchedWidthHeight()) {
             temp = Bitmap.createBitmap(cropBounds.height(), cropBounds.width(), mConfig);
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java
index 33a3f73..55f7918 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.filtershow.imageshow;
 
+import android.graphics.Rect;
 import android.graphics.RectF;
 
 public class GeometryMath {
@@ -99,4 +100,10 @@
         return Math.min(newWidth / oldWidth , newHeight / oldHeight);
     }
 
+    public static Rect roundNearest(RectF r){
+        Rect q = new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right),
+                Math.round(r.bottom));
+        return q;
+    }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
index cf52b15..dffdc24 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
@@ -59,10 +59,8 @@
         if (mStraightenRotation != 0) {
             return true;
         }
-        Rect cropBounds = new Rect();
-        mCropBounds.roundOut(cropBounds);
-        Rect photoBounds = new Rect();
-        mPhotoBounds.roundOut(photoBounds);
+        Rect cropBounds = GeometryMath.roundNearest(mCropBounds);
+        Rect photoBounds = GeometryMath.roundNearest(mPhotoBounds);
         if (!cropBounds.equals(photoBounds)) {
             return true;
         }
diff --git a/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java b/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java
index 17453d0..c717b6e 100644
--- a/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java
+++ b/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java
@@ -20,6 +20,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.widget.ImageButton;
@@ -32,6 +33,10 @@
     private static int mTextSize = 24;
     private static int mTextPadding = 20;
     private static Paint gPaint = new Paint();
+    private static Path gPath = new Path();
+    private static int mTrianglePadding = 2;
+    private static int mTriangleSize = 30;
+
     private Context mContext = null;
 
     public static void setTextSize(int value) {
@@ -42,6 +47,14 @@
         mTextPadding = value;
     }
 
+    public static void setTrianglePadding(int value) {
+        mTrianglePadding = value;
+    }
+
+    public static void setTriangleSize(int value) {
+        mTriangleSize = value;
+    }
+
     public void setText(String text) {
         mText = text;
         invalidate();
@@ -84,11 +97,25 @@
 
     @Override
     public void onDraw(Canvas canvas) {
-        gPaint.setARGB(255, 255, 255, 255);
+        gPaint.setARGB(96, 255, 255, 255);
         gPaint.setStrokeWidth(2);
         gPaint.setStyle(Paint.Style.STROKE);
-        canvas.drawRect(mTextPadding, mTextPadding, getWidth() - mTextPadding,
-                getHeight() - mTextPadding, gPaint);
+        int w = getWidth();
+        int h = getHeight();
+        canvas.drawRect(mTextPadding, mTextPadding, w - mTextPadding,
+                h - mTextPadding, gPaint);
+        gPath.reset();
+        gPath.moveTo(w - mTextPadding - mTrianglePadding - mTriangleSize,
+                     h - mTextPadding - mTrianglePadding);
+        gPath.lineTo(w - mTextPadding - mTrianglePadding,
+                     h - mTextPadding - mTrianglePadding - mTriangleSize);
+        gPath.lineTo(w - mTextPadding - mTrianglePadding,
+                     h - mTextPadding - mTrianglePadding);
+        gPath.close();
+        gPaint.setARGB(128, 255, 255, 255);
+        gPaint.setStrokeWidth(1);
+        gPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+        canvas.drawPath(gPath, gPaint);
         if (mText != null) {
             gPaint.reset();
             gPaint.setARGB(255, 255, 255, 255);
@@ -96,8 +123,8 @@
             float textWidth = gPaint.measureText(mText);
             Rect bounds = new Rect();
             gPaint.getTextBounds(mText, 0, mText.length(), bounds);
-            int x = (int) ((getWidth() - textWidth) / 2);
-            int y = (getHeight() + bounds.height()) / 2;
+            int x = (int) ((w - textWidth) / 2);
+            int y = (h + bounds.height()) / 2;
 
             canvas.drawText(mText, x, y, gPaint);
         }
diff --git a/src/com/android/gallery3d/ui/TiledTexture.java b/src/com/android/gallery3d/ui/TiledTexture.java
index ce3fcc6..02bde9f 100644
--- a/src/com/android/gallery3d/ui/TiledTexture.java
+++ b/src/com/android/gallery3d/ui/TiledTexture.java
@@ -55,7 +55,8 @@
 
     private int mUploadIndex = 0;
 
-    private final Tile[] mTiles;
+    private final Tile[] mTiles;  // Can be modified in different threads.
+                                  // Should be protected by "synchronized."
     private final int mWidth;
     private final int mHeight;
     private final RectF mSrcRect = new RectF();
@@ -91,7 +92,7 @@
             synchronized (this) {
                 long now = SystemClock.uptimeMillis();
                 long dueTime = now + UPLOAD_TILE_LIMIT;
-                while(now < dueTime && !deque.isEmpty()) {
+                while (now < dueTime && !deque.isEmpty()) {
                     TiledTexture t = deque.peekFirst();
                     if (t.uploadNextTile(canvas)) {
                         deque.removeFirst();
@@ -130,7 +131,7 @@
             int x = BORDER_SIZE - offsetX;
             int y = BORDER_SIZE - offsetY;
             int r = bitmap.getWidth() + x;
-            int b = bitmap.getHeight() + y ;
+            int b = bitmap.getHeight() + y;
             sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint);
             bitmap = null;
 
@@ -171,19 +172,21 @@
     private boolean uploadNextTile(GLCanvas canvas) {
         if (mUploadIndex == mTiles.length) return true;
 
-        Tile next = mTiles[mUploadIndex++];
+        synchronized (mTiles) {
+            Tile next = mTiles[mUploadIndex++];
 
-        // Make sure tile has not already been recycled by the time
-        // this is called (race condition in onGLIdle)
-        if (next.bitmap != null) {
-            boolean hasBeenLoad = next.isLoaded();
-            next.updateContent(canvas);
+            // Make sure tile has not already been recycled by the time
+            // this is called (race condition in onGLIdle)
+            if (next.bitmap != null) {
+                boolean hasBeenLoad = next.isLoaded();
+                next.updateContent(canvas);
 
-            // It will take some time for a texture to be drawn for the first
-            // time. When scrolling, we need to draw several tiles on the screen
-            // at the same time. It may cause a UI jank even these textures has
-            // been uploaded.
-            if (!hasBeenLoad) next.draw(canvas, 0, 0);
+                // It will take some time for a texture to be drawn for the first
+                // time. When scrolling, we need to draw several tiles on the screen
+                // at the same time. It may cause a UI jank even these textures has
+                // been uploaded.
+                if (!hasBeenLoad) next.draw(canvas, 0, 0);
+            }
         }
         return mUploadIndex == mTiles.length;
     }
@@ -212,9 +215,12 @@
         return mUploadIndex == mTiles.length;
     }
 
+    // Can be called in UI thread.
     public void recycle() {
-        for (int i = 0, n = mTiles.length; i < n; ++i) {
-            freeTile(mTiles[i]);
+        synchronized (mTiles) {
+            for (int i = 0, n = mTiles.length; i < n; ++i) {
+                freeTile(mTiles[i]);
+            }
         }
     }
 
@@ -263,15 +269,17 @@
             int x, int y, int width, int height) {
         RectF src = mSrcRect;
         RectF dest = mDestRect;
-        float scaleX = (float) width / mWidth ;
+        float scaleX = (float) width / mWidth;
         float scaleY = (float) height / mHeight;
-        for (int i = 0, n = mTiles.length; i < n; ++i) {
-            Tile t = mTiles[i];
-            src.set(0, 0, t.contentWidth, t.contentHeight);
-            src.offset(t.offsetX, t.offsetY);
-            mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
-            src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
-            canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
+        synchronized (mTiles) {
+            for (int i = 0, n = mTiles.length; i < n; ++i) {
+                Tile t = mTiles[i];
+                src.set(0, 0, t.contentWidth, t.contentHeight);
+                src.offset(t.offsetX, t.offsetY);
+                mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
+                src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+                canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
+            }
         }
     }
 
@@ -280,15 +288,17 @@
     public void draw(GLCanvas canvas, int x, int y, int width, int height) {
         RectF src = mSrcRect;
         RectF dest = mDestRect;
-        float scaleX = (float) width / mWidth ;
+        float scaleX = (float) width / mWidth;
         float scaleY = (float) height / mHeight;
-        for (int i = 0, n = mTiles.length; i < n; ++i) {
-            Tile t = mTiles[i];
-            src.set(0, 0, t.contentWidth, t.contentHeight);
-            src.offset(t.offsetX, t.offsetY);
-            mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
-            src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
-            canvas.drawTexture(t, mSrcRect, mDestRect);
+        synchronized (mTiles) {
+            for (int i = 0, n = mTiles.length; i < n; ++i) {
+                Tile t = mTiles[i];
+                src.set(0, 0, t.contentWidth, t.contentHeight);
+                src.offset(t.offsetX, t.offsetY);
+                mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
+                src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+                canvas.drawTexture(t, mSrcRect, mDestRect);
+            }
         }
     }
 
@@ -303,14 +313,16 @@
         float scaleX = target.width() / source.width();
         float scaleY = target.height() / source.height();
 
-        for (int i = 0, n = mTiles.length; i < n; ++i) {
-            Tile t = mTiles[i];
-            src.set(0, 0, t.contentWidth, t.contentHeight);
-            src.offset(t.offsetX, t.offsetY);
-            if (!src.intersect(source)) continue;
-            mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
-            src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
-            canvas.drawTexture(t, src, dest);
+        synchronized (mTiles) {
+            for (int i = 0, n = mTiles.length; i < n; ++i) {
+                Tile t = mTiles[i];
+                src.set(0, 0, t.contentWidth, t.contentHeight);
+                src.offset(t.offsetX, t.offsetY);
+                if (!src.intersect(source)) continue;
+                mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
+                src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+                canvas.drawTexture(t, src, dest);
+            }
         }
     }