OSDN Git Service

merge in lmp-mr1-release history after reset to 4816ed65c6425d31ea23f2c4f2e36c23c6f16b1c
authorThe Android Automerger <android-build@google.com>
Wed, 22 Oct 2014 13:10:36 +0000 (06:10 -0700)
committerThe Android Automerger <android-build@google.com>
Wed, 22 Oct 2014 13:10:36 +0000 (06:10 -0700)
29 files changed:
api/current.txt
core/java/android/content/Intent.java
core/java/android/preference/ListPreference.java
core/java/android/preference/SeekBarVolumizer.java
core/java/android/preference/VolumePreference.java
core/java/android/view/Surface.java
core/java/android/widget/TimePickerSpinnerDelegate.java
core/java/com/android/internal/app/ChooserActivity.java
core/java/com/android/internal/app/ResolverActivity.java
core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
core/jni/android_view_Surface.cpp
core/res/res/values/attrs.xml
graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
graphics/java/android/graphics/drawable/GradientDrawable.java
libs/hwui/renderthread/TimeLord.cpp
libs/hwui/tests/main.cpp
media/java/android/media/tv/TvInputService.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java [deleted file]
packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
services/core/java/com/android/server/am/ActivityStackSupervisor.java
services/core/java/com/android/server/wm/WindowManagerService.java
services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
telecomm/java/android/telecom/Connection.java
telephony/java/android/telephony/TelephonyManager.java
telephony/java/com/android/internal/telephony/ITelephony.aidl
tests/HwAccelerationTest/AndroidManifest.xml
tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java [new file with mode: 0644]
tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasTextureViewActivity.java [new file with mode: 0644]

index 5d134fd..241948c 100644 (file)
@@ -7432,6 +7432,7 @@ package android.content {
     method public java.lang.Object clone();
     method public android.content.Intent cloneFilter();
     method public static android.content.Intent createChooser(android.content.Intent, java.lang.CharSequence);
+    method public static android.content.Intent createChooser(android.content.Intent, java.lang.CharSequence, android.content.IntentSender);
     method public int describeContents();
     method public int fillIn(android.content.Intent, int);
     method public boolean filterEquals(android.content.Intent);
@@ -7707,6 +7708,8 @@ package android.content {
     field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
     field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
     field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
+    field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
+    field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
     field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
     field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
     field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
@@ -33370,6 +33373,7 @@ package android.view {
     method public int describeContents();
     method public boolean isValid();
     method public android.graphics.Canvas lockCanvas(android.graphics.Rect) throws java.lang.IllegalArgumentException, android.view.Surface.OutOfResourcesException;
+    method public android.graphics.Canvas lockHardwareCanvas();
     method public void readFromParcel(android.os.Parcel);
     method public void release();
     method public deprecated void unlockCanvas(android.graphics.Canvas);
index af6f181..7676e4b 100644 (file)
@@ -876,12 +876,44 @@ public class Intent implements Parcelable, Cloneable {
      * related methods.
      */
     public static Intent createChooser(Intent target, CharSequence title) {
+        return createChooser(target, title, null);
+    }
+
+    /**
+     * Convenience function for creating a {@link #ACTION_CHOOSER} Intent.
+     *
+     * <p>Builds a new {@link #ACTION_CHOOSER} Intent that wraps the given
+     * target intent, also optionally supplying a title.  If the target
+     * intent has specified {@link #FLAG_GRANT_READ_URI_PERMISSION} or
+     * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, then these flags will also be
+     * set in the returned chooser intent, with its ClipData set appropriately:
+     * either a direct reflection of {@link #getClipData()} if that is non-null,
+     * or a new ClipData built from {@link #getData()}.</p>
+     *
+     * <p>The caller may optionally supply an {@link IntentSender} to receive a callback
+     * when the user makes a choice. This can be useful if the calling application wants
+     * to remember the last chosen target and surface it as a more prominent or one-touch
+     * affordance elsewhere in the UI for next time.</p>
+     *
+     * @param target The Intent that the user will be selecting an activity
+     * to perform.
+     * @param title Optional title that will be displayed in the chooser.
+     * @param sender Optional IntentSender to be called when a choice is made.
+     * @return Return a new Intent object that you can hand to
+     * {@link Context#startActivity(Intent) Context.startActivity()} and
+     * related methods.
+     */
+    public static Intent createChooser(Intent target, CharSequence title, IntentSender sender) {
         Intent intent = new Intent(ACTION_CHOOSER);
         intent.putExtra(EXTRA_INTENT, target);
         if (title != null) {
             intent.putExtra(EXTRA_TITLE, title);
         }
 
+        if (sender != null) {
+            intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender);
+        }
+
         // Migrate any clip data and flags from target.
         int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
                 | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
@@ -3140,6 +3172,26 @@ public class Intent implements Parcelable, Cloneable {
             "android.intent.extra.REPLACEMENT_EXTRAS";
 
     /**
+     * An {@link IntentSender} that will be notified if a user successfully chooses a target
+     * component to handle an action in an {@link #ACTION_CHOOSER} activity. The IntentSender
+     * will have the extra {@link #EXTRA_CHOSEN_COMPONENT} appended to it containing the
+     * {@link ComponentName} of the chosen component.
+     *
+     * <p>In some situations this callback may never come, for example if the user abandons
+     * the chooser, switches to another task or any number of other reasons. Apps should not
+     * be written assuming that this callback will always occur.</p>
+     */
+    public static final String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER =
+            "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+
+    /**
+     * The {@link ComponentName} chosen by the user to complete an action.
+     *
+     * @see #EXTRA_CHOSEN_COMPONENT_INTENT_SENDER
+     */
+    public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
+
+    /**
      * A {@link android.view.KeyEvent} object containing the event that
      * triggered the creation of the Intent it is in.
      */
index 8081a54..9482a72 100644 (file)
@@ -162,10 +162,10 @@ public class ListPreference extends DialogPreference {
     @Override
     public CharSequence getSummary() {
         final CharSequence entry = getEntry();
-        if (mSummary == null || entry == null) {
+        if (mSummary == null) {
             return super.getSummary();
         } else {
-            return String.format(mSummary, entry);
+            return String.format(mSummary, entry == null ? "" : entry);
         }
     }
 
index a680b51..3130b64 100644 (file)
@@ -130,6 +130,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
     }
 
     private void postStartSample() {
+        if (mHandler == null) return;
         mHandler.removeMessages(MSG_START_SAMPLE);
         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
                 isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
@@ -150,7 +151,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
         }
     }
 
-    void postStopSample() {
+    private void postStopSample() {
+        if (mHandler == null) return;
         // remove pending delayed start messages
         mHandler.removeMessages(MSG_START_SAMPLE);
         mHandler.removeMessages(MSG_STOP_SAMPLE);
@@ -200,7 +202,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
         postSetVolume(progress);
     }
 
-    void postSetVolume(int progress) {
+    private void postSetVolume(int progress) {
+        if (mHandler == null) return;
         // Do the volume changing separately to give responsive UI
         mLastProgress = progress;
         mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
index 86d96f2..0d4c0b6 100644 (file)
@@ -117,7 +117,7 @@ public class VolumePreference extends SeekBarDialogPreference implements
 
     public void onActivityStop() {
         if (mSeekBarVolumizer != null) {
-            mSeekBarVolumizer.postStopSample();
+            mSeekBarVolumizer.stopSample();
         }
     }
 
index 3770b8a..132e25c 100644 (file)
@@ -87,6 +87,8 @@ public class Surface implements Parcelable {
     // non compatibility mode.
     private Matrix mCompatibleMatrix;
 
+    private HwuiContext mHwuiContext;
+
     /** @hide */
     @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
     @Retention(RetentionPolicy.SOURCE)
@@ -171,6 +173,10 @@ public class Surface implements Parcelable {
                 nativeRelease(mNativeObject);
                 setNativeObjectLocked(0);
             }
+            if (mHwuiContext != null) {
+                mHwuiContext.destroy();
+                mHwuiContext = null;
+            }
         }
     }
 
@@ -264,27 +270,60 @@ public class Surface implements Parcelable {
      * @param canvas The canvas previously obtained from {@link #lockCanvas}.
      */
     public void unlockCanvasAndPost(Canvas canvas) {
+        synchronized (mLock) {
+            checkNotReleasedLocked();
+
+            if (mHwuiContext != null) {
+                mHwuiContext.unlockAndPost(canvas);
+            } else {
+                unlockSwCanvasAndPost(canvas);
+            }
+        }
+    }
+
+    private void unlockSwCanvasAndPost(Canvas canvas) {
         if (canvas != mCanvas) {
             throw new IllegalArgumentException("canvas object must be the same instance that "
                     + "was previously returned by lockCanvas");
         }
+        if (mNativeObject != mLockedObject) {
+            Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
+                    Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
+                    Long.toHexString(mLockedObject) +")");
+        }
+        if (mLockedObject == 0) {
+            throw new IllegalStateException("Surface was not locked");
+        }
+        try {
+            nativeUnlockCanvasAndPost(mLockedObject, canvas);
+        } finally {
+            nativeRelease(mLockedObject);
+            mLockedObject = 0;
+        }
+    }
 
+    /**
+     * Gets a {@link Canvas} for drawing into this surface.
+     *
+     * After drawing into the provided {@link Canvas}, the caller must
+     * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+     *
+     * Unlike {@link #lockCanvas(Rect)} this will return a hardware-accelerated
+     * canvas. See the <a href="{@docRoot}guide/topics/graphics/hardware-accel.html#unsupported">
+     * unsupported drawing operations</a> for a list of what is and isn't
+     * supported in a hardware-accelerated canvas.
+     *
+     * @return A canvas for drawing into the surface.
+     *
+     * @throws IllegalStateException If the canvas cannot be locked.
+     */
+    public Canvas lockHardwareCanvas() {
         synchronized (mLock) {
             checkNotReleasedLocked();
-            if (mNativeObject != mLockedObject) {
-                Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
-                        Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
-                        Long.toHexString(mLockedObject) +")");
-            }
-            if (mLockedObject == 0) {
-                throw new IllegalStateException("Surface was not locked");
-            }
-            try {
-                nativeUnlockCanvasAndPost(mLockedObject, canvas);
-            } finally {
-                nativeRelease(mLockedObject);
-                mLockedObject = 0;
+            if (mHwuiContext == null) {
+                mHwuiContext = new HwuiContext();
             }
+            return mHwuiContext.lockCanvas();
         }
     }
 
@@ -415,6 +454,9 @@ public class Surface implements Parcelable {
             }
             mNativeObject = ptr;
             mGenerationId += 1;
+            if (mHwuiContext != null) {
+                mHwuiContext.updateSurface();
+            }
         }
     }
 
@@ -518,4 +560,59 @@ public class Surface implements Parcelable {
             mOrigMatrix.set(m);
         }
     }
+
+    private final class HwuiContext {
+        private final RenderNode mRenderNode;
+        private long mHwuiRenderer;
+        private HardwareCanvas mCanvas;
+
+        HwuiContext() {
+            mRenderNode = RenderNode.create("HwuiCanvas", null);
+            mRenderNode.setClipToBounds(false);
+            mHwuiRenderer = nHwuiCreate(mRenderNode.mNativeRenderNode, mNativeObject);
+        }
+
+        Canvas lockCanvas() {
+            if (mCanvas != null) {
+                throw new IllegalStateException("Surface was already locked!");
+            }
+            mCanvas = mRenderNode.start(0, 0);
+            return mCanvas;
+        }
+
+        void unlockAndPost(Canvas canvas) {
+            if (canvas != mCanvas) {
+                throw new IllegalArgumentException("canvas object must be the same instance that "
+                        + "was previously returned by lockCanvas");
+            }
+            mRenderNode.end(mCanvas);
+            mCanvas = null;
+            nHwuiDraw(mHwuiRenderer);
+        }
+
+        void updateSurface() {
+            nHwuiSetSurface(mHwuiRenderer, mNativeObject);
+        }
+
+        void destroy() {
+            if (mHwuiRenderer != 0) {
+                nHwuiDestroy(mHwuiRenderer);
+                mHwuiRenderer = 0;
+            }
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                destroy();
+            } finally {
+                super.finalize();
+            }
+        }
+    }
+
+    private static native long nHwuiCreate(long rootNode, long surface);
+    private static native void nHwuiSetSurface(long renderer, long surface);
+    private static native void nHwuiDraw(long renderer);
+    private static native void nHwuiDestroy(long renderer);
 }
index e4342b1..d9c4114 100644 (file)
@@ -105,6 +105,10 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
     private String mMinutePickerDescription;
     private String mSelectMinutes;
 
+    // Most recent time announcement values for accessibility.
+    private CharSequence mLastAnnouncedText;
+    private boolean mLastAnnouncedIsHour;
+
     private Calendar mTempCalendar;
 
     public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
@@ -224,11 +228,11 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
         // Enable or disable the AM/PM view.
         updateHeaderAmPm();
         // Update Hour and Minutes
-        updateHeaderHour(mInitialHourOfDay, true);
+        updateHeaderHour(mInitialHourOfDay, false);
         // Update time separator
         updateHeaderSeparator();
         // Update Minutes
-        updateHeaderMinute(mInitialMinute);
+        updateHeaderMinute(mInitialMinute, false);
         // Invalidate everything
         mDelegator.invalidate();
     }
@@ -293,7 +297,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
             return;
         }
         mInitialHourOfDay = currentHour;
-        updateHeaderHour(currentHour, true /* accessibility announce */);
+        updateHeaderHour(currentHour, true);
         updateHeaderAmPm();
         mRadialTimePickerView.setCurrentHour(currentHour);
         mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
@@ -329,7 +333,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
             return;
         }
         mInitialMinute = currentMinute;
-        updateHeaderMinute(currentMinute);
+        updateHeaderMinute(currentMinute, true);
         mRadialTimePickerView.setCurrentMinute(currentMinute);
         mDelegator.invalidate();
         onTimeChanged();
@@ -357,7 +361,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
         generateLegalTimesTree();
         int hour = mRadialTimePickerView.getCurrentHour();
         mInitialHourOfDay = hour;
-        updateHeaderHour(hour, false /* no accessibility announce */);
+        updateHeaderHour(hour, false);
         updateHeaderAmPm();
         updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing());
         mDelegator.invalidate();
@@ -604,19 +608,17 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
     @Override
     public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
         if (pickerIndex == HOUR_INDEX) {
-            updateHeaderHour(newValue, false);
-            String announcement = String.format("%d", newValue);
             if (mAllowAutoAdvance && autoAdvance) {
+                updateHeaderHour(newValue, false);
                 setCurrentItemShowing(MINUTE_INDEX, true, false);
-                announcement += ". " + mSelectMinutes;
+                mRadialTimePickerView.announceForAccessibility(newValue + ". " + mSelectMinutes);
             } else {
+                updateHeaderHour(newValue, true);
                 mRadialTimePickerView.setContentDescription(
                         mHourPickerDescription + ": " + newValue);
             }
-
-            mRadialTimePickerView.announceForAccessibility(announcement);
         } else if (pickerIndex == MINUTE_INDEX){
-            updateHeaderMinute(newValue);
+            updateHeaderMinute(newValue, true);
             mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + newValue);
         } else if (pickerIndex == AMPM_INDEX) {
             updateAmPmLabelStates(newValue);
@@ -664,7 +666,16 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
         CharSequence text = String.format(format, value);
         mHourView.setText(text);
         if (announce) {
-            mRadialTimePickerView.announceForAccessibility(text);
+            tryAnnounceForAccessibility(text, true);
+        }
+    }
+
+    private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) {
+        if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
+            // TODO: Find a better solution, potentially live regions?
+            mDelegator.announceForAccessibility(text);
+            mLastAnnouncedText = text;
+            mLastAnnouncedIsHour = isHour;
         }
     }
 
@@ -715,13 +726,15 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
         return -1;
     }
 
-    private void updateHeaderMinute(int value) {
+    private void updateHeaderMinute(int value, boolean announceForAccessibility) {
         if (value == 60) {
             value = 0;
         }
-        CharSequence text = String.format(mCurrentLocale, "%02d", value);
-        mRadialTimePickerView.announceForAccessibility(text);
+        final CharSequence text = String.format(mCurrentLocale, "%02d", value);
         mMinuteView.setText(text);
+        if (announceForAccessibility) {
+            tryAnnounceForAccessibility(text, false);
+        }
     }
 
     /**
@@ -921,8 +934,8 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate im
         if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
             int hour = mRadialTimePickerView.getCurrentHour();
             int minute = mRadialTimePickerView.getCurrentMinute();
-            updateHeaderHour(hour, true);
-            updateHeaderMinute(minute);
+            updateHeaderHour(hour, false);
+            updateHeaderMinute(minute, false);
             if (!mIs24HourView) {
                 updateAmPmLabelStates(hour < 12 ? AM : PM);
             }
index 5267811..0bc1a8d 100644 (file)
 
 package com.android.internal.app;
 
+import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.util.Log;
+import android.util.Slog;
 
 public class ChooserActivity extends ResolverActivity {
+    private static final String TAG = "ChooserActivity";
+
     private Bundle mReplacementExtras;
+    private IntentSender mChosenComponentSender;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -60,11 +67,14 @@ public class ChooserActivity extends ResolverActivity {
                 initialIntents[i] = in;
             }
         }
+        mChosenComponentSender = intent.getParcelableExtra(
+                Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
         setSafeForwardingMode(true);
         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
                 null, false);
     }
 
+    @Override
     public Intent getReplacementIntent(String packageName, Intent defIntent) {
         if (mReplacementExtras != null) {
             final Bundle replExtras = mReplacementExtras.getBundle(packageName);
@@ -77,6 +87,22 @@ public class ChooserActivity extends ResolverActivity {
         return defIntent;
     }
 
+    @Override
+    public void onActivityStarted(Intent intent) {
+        if (mChosenComponentSender != null) {
+            final ComponentName target = intent.getComponent();
+            if (target != null) {
+                final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
+                try {
+                    mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
+                } catch (IntentSender.SendIntentException e) {
+                    Slog.e(TAG, "Unable to launch supplied IntentSender to report "
+                            + "the chosen component: " + e);
+                }
+            }
+        }
+    }
+
     private void modifyTargetIntent(Intent in) {
         final String action = in.getAction();
         if (Intent.ACTION_SEND.equals(action) ||
index b9cbc62..ccffa19 100644 (file)
@@ -644,10 +644,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
     public void safelyStartActivity(Intent intent) {
         if (!mSafeForwardingMode) {
             startActivity(intent);
+            onActivityStarted(intent);
             return;
         }
         try {
             startActivityAsCaller(intent, null, UserHandle.USER_NULL);
+            onActivityStarted(intent);
         } catch (RuntimeException e) {
             String launchedFromPackage;
             try {
@@ -662,6 +664,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
         }
     }
 
+    public void onActivityStarted(Intent intent) {
+        // Do nothing
+    }
+
     void showAppDetails(ResolveInfo ri) {
         Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
index b64ab0d..a67740c 100644 (file)
@@ -78,6 +78,8 @@ private:
                 env->ExceptionDescribe();
                 env->ExceptionClear();
                 SkDebugf("---- read threw an exception\n");
+                // Consider the stream to be at the end, since there was an error.
+                fIsAtEnd = true;
                 return 0;
             }
 
@@ -92,6 +94,9 @@ private:
                 env->ExceptionDescribe();
                 env->ExceptionClear();
                 SkDebugf("---- read:GetByteArrayRegion threw an exception\n");
+                // The error was not with the stream itself, but consider it to be at the
+                // end, since we do not have a way to recover.
+                fIsAtEnd = true;
                 return 0;
             }
 
index 9bb8195..b3d9890 100644 (file)
 
 #include <ScopedUtfChars.h>
 
+#include <AnimationContext.h>
+#include <DisplayListRenderer.h>
+#include <RenderNode.h>
+#include <renderthread/RenderProxy.h>
+
 // ----------------------------------------------------------------------------
 
 namespace android {
@@ -352,8 +357,53 @@ static void nativeWriteToParcel(JNIEnv* env, jclass clazz,
     parcel->writeStrongBinder( self != 0 ? self->getIGraphicBufferProducer()->asBinder() : NULL);
 }
 
+namespace uirenderer {
+
+using namespace android::uirenderer::renderthread;
+
+class ContextFactory : public IContextFactory {
+public:
+    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) {
+        return new AnimationContext(clock);
+    }
+};
+
+static jlong create(JNIEnv* env, jclass clazz, jlong rootNodePtr, jlong surfacePtr) {
+    RenderNode* rootNode = reinterpret_cast<RenderNode*>(rootNodePtr);
+    sp<Surface> surface(reinterpret_cast<Surface*>(surfacePtr));
+    ContextFactory factory;
+    RenderProxy* proxy = new RenderProxy(false, rootNode, &factory);
+    proxy->loadSystemProperties();
+    proxy->initialize(surface);
+    // Shadows can't be used via this interface, so just set the light source
+    // to all 0s. (and width & height are unused, TODO remove them)
+    proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0);
+    return (jlong) proxy;
+}
+
+static void setSurface(JNIEnv* env, jclass clazz, jlong rendererPtr, jlong surfacePtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(rendererPtr);
+    sp<Surface> surface(reinterpret_cast<Surface*>(surfacePtr));
+    proxy->updateSurface(surface);
+}
+
+static void draw(JNIEnv* env, jclass clazz, jlong rendererPtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(rendererPtr);
+    nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
+    proxy->syncAndDrawFrame(frameTimeNs, 0, 1.0f);
+}
+
+static void destroy(JNIEnv* env, jclass clazz, jlong rendererPtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(rendererPtr);
+    delete proxy;
+}
+
+} // uirenderer
+
 // ----------------------------------------------------------------------------
 
+namespace hwui = android::uirenderer;
+
 static JNINativeMethod gSurfaceMethods[] = {
     {"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
             (void*)nativeCreateFromSurfaceTexture },
@@ -375,6 +425,12 @@ static JNINativeMethod gSurfaceMethods[] = {
             (void*)nativeReadFromParcel },
     {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
             (void*)nativeWriteToParcel },
+
+    // HWUI context
+    {"nHwuiCreate", "(JJ)J", (void*) hwui::create },
+    {"nHwuiSetSurface", "(JJ)V", (void*) hwui::setSurface },
+    {"nHwuiDraw", "(J)V", (void*) hwui::draw },
+    {"nHwuiDestroy", "(J)V", (void*) hwui::destroy },
 };
 
 int register_android_view_Surface(JNIEnv* env)
index 747cb14..d988480 100644 (file)
         <!-- Y coordinate of the origin of the gradient within the shape. -->
         <attr name="centerY" format="float|fraction" />
         <!-- Radius of the gradient, used only with radial gradient. -->
-        <attr name="gradientRadius" format="float|fraction" />
+        <attr name="gradientRadius" format="float|fraction|dimension" />
     </declare-styleable>
 
     <!-- Used to fill the shape of GradientDrawable with a solid color. -->
index 9bcad3e..849faec 100644 (file)
@@ -139,27 +139,15 @@ public class AnimatedStateListDrawable extends StateListDrawable {
 
     @Override
     protected boolean onStateChange(int[] stateSet) {
-        final int keyframeIndex = mState.indexOfKeyframe(stateSet);
-        if (keyframeIndex == getCurrentIndex()) {
-            // Propagate state change to current keyframe.
-            final Drawable current = getCurrent();
-            if (current != null) {
-                return current.setState(stateSet);
-            }
-            return false;
-        }
-
-        // Attempt to find a valid transition to the keyframe.
-        if (selectTransition(keyframeIndex)) {
-            return true;
-        }
-
-        // No valid transition, attempt to jump directly to the keyframe.
-        if (selectDrawable(keyframeIndex)) {
-            return true;
-        }
-
-        return super.onStateChange(stateSet);
+        // If we're not already at the target index, either attempt to find a
+        // valid transition to it or jump directly there.
+        final int targetIndex = mState.indexOfKeyframe(stateSet);
+        final boolean changedIndex = targetIndex != getCurrentIndex()
+                && (selectTransition(targetIndex) || selectDrawable(targetIndex));
+
+        // Always call super.onStateChanged() to propagate the state change to
+        // the current drawable.
+        return super.onStateChange(stateSet) || changedIndex;
     }
 
     private boolean selectTransition(int toIndex) {
index eff5a3d..f6a78f1 100644 (file)
@@ -1395,9 +1395,12 @@ public class GradientDrawable extends Drawable {
                     } else {
                         radiusType = RADIUS_TYPE_FRACTION;
                     }
-                } else {
+                } else if (tv.type == TypedValue.TYPE_DIMENSION) {
                     radius = tv.getDimension(r.getDisplayMetrics());
                     radiusType = RADIUS_TYPE_PIXELS;
+                } else {
+                    radius = tv.getFloat();
+                    radiusType = RADIUS_TYPE_PIXELS;
                 }
 
                 st.mGradientRadius = radius;
index cf3d039..9bd4eae 100644 (file)
@@ -20,7 +20,7 @@ namespace uirenderer {
 namespace renderthread {
 
 TimeLord::TimeLord()
-        : mFrameIntervalNanos(0)
+        : mFrameIntervalNanos(milliseconds_to_nanoseconds(16))
         , mFrameTimeNanos(0) {
 }
 
index 3e9a86f..2b80d56 100644 (file)
@@ -81,8 +81,8 @@ int main(int argc, char* argv[]) {
     rootNode->mutateStagingProperties().setClipToBounds(false);
     rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
 
-    RenderProxy* proxy = new RenderProxy(false, rootNode, new ContextFactory());
-    proxy->setFrameInterval(milliseconds_to_nanoseconds(16));
+    ContextFactory factory;
+    RenderProxy* proxy = new RenderProxy(false, rootNode, &factory);
     proxy->loadSystemProperties();
     proxy->initialize(surface);
     float lightX = width / 2.0;
index 4f8facb..819affc 100644 (file)
@@ -1067,9 +1067,19 @@ public abstract class TvInputService extends Service {
 
     /**
      * Base class for a TV input session which represents an external device connected to a
-     * hardware TV input. Once TV input returns an implementation of this class on
-     * {@link #onCreateSession(String)}, the framework will create a hardware session and forward
-     * the application's surface to the hardware TV input.
+     * hardware TV input.
+     * <p>
+     * This class is for an input which provides channels for the external set-top box to the
+     * application. Once a TV input returns an implementation of this class on
+     * {@link #onCreateSession(String)}, the framework will create a separate session for
+     * a hardware TV Input (e.g. HDMI 1) and forward the application's surface to the session so
+     * that the user can see the screen of the hardware TV Input when she tunes to a channel from
+     * this TV input. The implementation of this class is expected to change the channel of the
+     * external set-top box via a proprietary protocol when {@link HardwareSession#onTune(Uri)} is
+     * requested by the application.
+     * </p><p>
+     * Note that this class is not for inputs for internal hardware like built-in tuner and HDMI 1.
+     * </p>
      * @see #onCreateSession(String)
      */
     public abstract static class HardwareSession extends Session {
index 4980c1c..e5c9bdd 100644 (file)
@@ -175,6 +175,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
         DragDownHelper.DragDownCallback, ActivityStarter {
@@ -2963,6 +2964,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
         if (mSecurityController != null) {
             mSecurityController.dump(fd, pw, args);
         }
+        pw.println("SharedPreferences:");
+        for (Map.Entry<String, ?> entry : mContext.getSharedPreferences(mContext.getPackageName(),
+                Context.MODE_PRIVATE).getAll().entrySet()) {
+            pw.print("  "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
+        }
     }
 
     private String hunStateToString(Entry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
deleted file mode 100644 (file)
index f339401..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-public class Prefs {
-    private static final String SHARED_PREFS_NAME = "status_bar";
-
-    public static SharedPreferences read(Context context) {
-        return context.getSharedPreferences(Prefs.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
-    }
-
-    public static SharedPreferences.Editor edit(Context context) {
-        return context.getSharedPreferences(Prefs.SHARED_PREFS_NAME, Context.MODE_PRIVATE).edit();
-    }
-}
index ea431ae..69be377 100644 (file)
@@ -430,7 +430,8 @@ public class ZenModePanel extends LinearLayout {
         }
         tag.condition = condition;
         tag.rb.setEnabled(enabled);
-        if (sameConditionId(mSessionExitCondition, tag.condition)) {
+        if (mSessionExitCondition != null
+                && sameConditionId(mSessionExitCondition, tag.condition)) {
             tag.rb.setChecked(true);
         }
         tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@@ -690,7 +691,7 @@ public class ZenModePanel extends LinearLayout {
         }
 
         private SharedPreferences prefs() {
-            return mContext.getSharedPreferences(ZenModePanel.class.getSimpleName(), 0);
+            return mContext.getSharedPreferences(mContext.getPackageName(), 0);
         }
 
         private void updateMinuteIndex() {
index 03dd3c0..c1bf955 100644 (file)
@@ -120,7 +120,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
     static final boolean DEBUG_RELEASE = DEBUG || false;
     static final boolean DEBUG_SAVED_STATE = DEBUG || false;
     static final boolean DEBUG_SCREENSHOTS = DEBUG || false;
-    static final boolean DEBUG_STATES = DEBUG || false;
+    static final boolean DEBUG_STATES = DEBUG || true;
     static final boolean DEBUG_VISIBLE_BEHIND = DEBUG || false;
 
     public static final int HOME_STACK_ID = 0;
index f82aee4..0a0e8a1 100644 (file)
@@ -2509,9 +2509,8 @@ public class WindowManagerService extends IWindowManager.Stub
             }
             mInputMonitor.updateInputWindowsLw(false /*force*/);
 
-            if (localLOGV) Slog.v(
-                TAG, "New client " + client.asBinder()
-                + ": window=" + win);
+            if (true || localLOGV) Slog.v(TAG, "addWindow: New client " + client.asBinder()
+                    + ": window=" + win + " Callers=" + Debug.getCallers(5));
 
             if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
                 reportNewConfig = true;
@@ -2675,7 +2674,8 @@ public class WindowManagerService extends IWindowManager.Stub
         mPolicy.removeWindowLw(win);
         win.removeLocked();
 
-        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "removeWindowInnerLocked: " + win);
+        if (true || DEBUG_ADD_REMOVE) Slog.v(TAG, "removeWindowInnerLocked: " + win +
+                " Callers=" + Debug.getCallers(5));
         mWindowMap.remove(win.mClient.asBinder());
         if (win.mAppOp != AppOpsManager.OP_NONE) {
             mAppOps.finishOp(win.mAppOp, win.getOwningUid(), win.getOwningPackage());
index fe4b7b9..9ee44b9 100644 (file)
@@ -1687,7 +1687,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                 .setContentTitle(mContext.getString(R.string.ssl_ca_cert_warning))
                 .setContentText(contentText)
                 .setContentIntent(notifyIntent)
-                .setOngoing(true)
                 .setPriority(Notification.PRIORITY_HIGH)
                 .setShowWhen(false)
                 .setColor(mContext.getResources().getColor(
index 44fa157..795053d 100644 (file)
@@ -927,6 +927,7 @@ public abstract class Connection {
             Log.d(this, "Conference reset");
             mConference = null;
             fireConferenceChanged();
+            onConferenceChanged();
         }
     }
 
index d3cef4a..7b5234a 100644 (file)
@@ -3528,4 +3528,25 @@ public class TelephonyManager {
         }
         return -1;
     }
+
+    /** @hide */
+    @SystemApi
+    public void enableVideoCalling(boolean enable) {
+        try {
+            getITelephony().enableVideoCalling(enable);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#enableVideoCalling", e);
+        }
+    }
+
+    /** @hide */
+    @SystemApi
+    public boolean isVideoCallingEnabled() {
+        try {
+            return getITelephony().isVideoCallingEnabled();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isVideoCallingEnabled", e);
+        }
+        return false;
+    }
 }
index 58807b2..b4d165c 100644 (file)
@@ -28,7 +28,7 @@ import java.util.List;
 /**
  * Interface used to interact with the phone.  Mostly this is used by the
  * TelephonyManager class.  A few places are still using this directly.
- * Please clean them up if possible and use TelephonyManager insteadl.
+ * Please clean them up if possible and use TelephonyManager instead.
  *
  * {@hide}
  */
@@ -831,4 +831,18 @@ interface ITelephony {
      * @return phone radio type and access technology
      */
     int getRadioAccessFamily(in int phoneId);
+
+    /**
+     * Enables or disables video calling.
+     *
+     * @param enable Whether to enable video calling.
+     */
+    void enableVideoCalling(boolean enable);
+
+    /**
+     * Whether video calling has been enabled by the user.
+     *
+     * @return {@code True} if the user has enabled video calling, {@code false} otherwise.
+     */
+    boolean isVideoCallingEnabled();
 }
index bc2f1fd..8531944 100644 (file)
         </activity>
 
         <activity
+                android:name="HardwareCanvasTextureViewActivity"
+                android:label="TextureView/HardwareCanvas">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="HardwareCanvasSurfaceViewActivity"
+                android:label="SurfaceView/HardwareCanvas">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
                 android:name="GLTextureViewActivity"
                 android:label="TextureView/OpenGL">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java
new file mode 100644 (file)
index 0000000..b1431c5
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class HardwareCanvasSurfaceViewActivity extends Activity implements Callback {
+    private SurfaceView mSurfaceView;
+    private HardwareCanvasSurfaceViewActivity.RenderingThread mThread;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FrameLayout content = new FrameLayout(this);
+
+        mSurfaceView = new SurfaceView(this);
+        mSurfaceView.getHolder().addCallback(this);
+
+        content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                Gravity.CENTER));
+        setContentView(content);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        mThread = new RenderingThread(holder.getSurface());
+        mThread.start();
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        mThread.setSize(width, height);
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        if (mThread != null) mThread.stopRendering();
+    }
+
+    private static class RenderingThread extends Thread {
+        private final Surface mSurface;
+        private volatile boolean mRunning = true;
+        private int mWidth, mHeight;
+
+        public RenderingThread(Surface surface) {
+            mSurface = surface;
+        }
+
+        void setSize(int width, int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        @Override
+        public void run() {
+            float x = 0.0f;
+            float y = 0.0f;
+            float speedX = 5.0f;
+            float speedY = 3.0f;
+
+            Paint paint = new Paint();
+            paint.setColor(0xff00ff00);
+
+            while (mRunning && !Thread.interrupted()) {
+                final Canvas canvas = mSurface.lockHardwareCanvas();
+                try {
+                    canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+                    canvas.drawRect(x, y, x + 20.0f, y + 20.0f, paint);
+                } finally {
+                    mSurface.unlockCanvasAndPost(canvas);
+                }
+
+                if (x + 20.0f + speedX >= mWidth || x + speedX <= 0.0f) {
+                    speedX = -speedX;
+                }
+                if (y + 20.0f + speedY >= mHeight || y + speedY <= 0.0f) {
+                    speedY = -speedY;
+                }
+
+                x += speedX;
+                y += speedY;
+
+                try {
+                    Thread.sleep(15);
+                } catch (InterruptedException e) {
+                    // Interrupted
+                }
+            }
+        }
+
+        void stopRendering() {
+            interrupt();
+            mRunning = false;
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasTextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasTextureViewActivity.java
new file mode 100644 (file)
index 0000000..63a6efa
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.SurfaceTexture;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.TextureView;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class HardwareCanvasTextureViewActivity extends Activity
+        implements TextureView.SurfaceTextureListener {
+    private TextureView mTextureView;
+    private HardwareCanvasTextureViewActivity.RenderingThread mThread;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FrameLayout content = new FrameLayout(this);
+
+        mTextureView = new TextureView(this);
+        mTextureView.setSurfaceTextureListener(this);
+        mTextureView.setOpaque(false);
+
+        content.addView(mTextureView, new FrameLayout.LayoutParams(500, 500, Gravity.CENTER));
+        setContentView(content);
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        mThread = new RenderingThread(mTextureView);
+        mThread.start();
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Ignored
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        if (mThread != null) mThread.stopRendering();
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        // Ignored
+    }
+
+    private static class RenderingThread extends Thread {
+        private final TextureView mView;
+        private final Surface mSurface;
+        private volatile boolean mRunning = true;
+
+        public RenderingThread(TextureView view) {
+            mView = view;
+            mSurface = new Surface(mView.getSurfaceTexture());
+        }
+
+        @Override
+        public void run() {
+            float x = 0.0f;
+            float y = 0.0f;
+            float speedX = 5.0f;
+            float speedY = 3.0f;
+
+            Paint paint = new Paint();
+            paint.setColor(0xff00ff00);
+
+            while (mRunning && !Thread.interrupted()) {
+                final Canvas canvas = mSurface.lockHardwareCanvas();
+                try {
+                    canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+                    canvas.drawRect(x, y, x + 20.0f, y + 20.0f, paint);
+                } finally {
+                    mSurface.unlockCanvasAndPost(canvas);
+                }
+
+                if (x + 20.0f + speedX >= mView.getWidth() || x + speedX <= 0.0f) {
+                    speedX = -speedX;
+                }
+                if (y + 20.0f + speedY >= mView.getHeight() || y + speedY <= 0.0f) {
+                    speedY = -speedY;
+                }
+
+                x += speedX;
+                y += speedY;
+
+                try {
+                    Thread.sleep(15);
+                } catch (InterruptedException e) {
+                    // Interrupted
+                }
+            }
+        }
+
+        void stopRendering() {
+            interrupt();
+            mRunning = false;
+        }
+    }
+}