Changes to use replica island in CTS OpenGl performance test
- add localization to make build work
- changed start level to repeat Gl rendering
- add position reset in Buffer access to fix crash issue after
   too fast task switch
- add watchdog timer to detect Gl rendering freeze

Change-Id: Iaf16ecdda6dec3b268d3322a405666aec92db6bf
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..4bc3206
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2012 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.
+
+# for building replica island only for CTS build
+# Note that there is no Android.mk under the replicaisland folder
+LOCAL_PATH:= external/replicaisland
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_JAVA_LIBRARIES :=
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := replicaisland
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4069c42..87bf530 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,28 +1,52 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.replica.replicaisland" 
     android:versionName="1.4" 
-    android:versionCode="14"
-    android:installLocation="preferExternal">
-    
+    android:versionCode="14" >
+
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission xmlns:android="http://schemas.android.com/apk/res/android"
+                     android:name="android.permission.INTERNET"/>
+
+    <!-- Actually, this game works fine on small screens, but the only one out
+	right now has no 3D acceleration, so it's slow and unplayable. -->
+	<supports-screens android:largeScreens="true"
+	                  android:smallScreens="false"
+					  android:anyDensity="true"
+					  android:normalScreens="true"/>
     <application android:icon="@drawable/icon"
     			 android:label="@string/app_name"
-    			 android:debuggable="false"
+                 android:debuggable="true"
     			 android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
     	<activity android:name=".MainMenuActivity"
                   android:label="@string/app_name" 
                   android:screenOrientation="landscape" 
                   android:configChanges="keyboardHidden|orientation" >
-        	<intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
         </activity>
         
         <activity android:name="AndouKun" android:label="@string/app_name" 
    				  android:screenOrientation="landscape" 
      			  android:configChanges="keyboardHidden|orientation" 
-     			  android:launchMode="singleTask"/>
+                  android:launchMode="singleTask">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
             
         <activity android:name="LevelSelectActivity"
 	              android:screenOrientation="landscape" 
@@ -59,19 +83,6 @@
 	   			  android:configChanges="keyboardHidden|orientation"/>
 	   			     
 	</application>
-    <uses-permission android:name="android.permission.VIBRATE"/>
-    <uses-permission xmlns:android="http://schemas.android.com/apk/res/android" 
-                     android:name="android.permission.INTERNET"/>
-	<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="8"/>
-    
-	<!-- Actually, this game works fine on small screens, but the only one out 
-	right now has no 3D acceleration, so it's slow and unplayable. --> 
-	<supports-screens android:largeScreens="true"
-	                  android:smallScreens="false" 
-					  android:anyDensity="true"
-					  android:normalScreens="true"/>
-	<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>  -->
-	
-	
+
 </manifest> 
 
diff --git a/res/layout/conversation_dialog.xml b/res/layout/conversation_dialog.xml
index 3528ec6..c03d3ff 100644
--- a/res/layout/conversation_dialog.xml
+++ b/res/layout/conversation_dialog.xml
@@ -14,7 +14,7 @@
 			    <TextView android:id="@+id/speakername"
 					    android:layout_width="wrap_content" 
 					    android:layout_height="20dp"
-					    android:text="Wanda"
+					    android:text="@string/cd_Wanda"
 					    android:textSize = "15sp"
 					    android:textStyle = "bold"/>
 		   	 	</TableRow>
@@ -25,7 +25,7 @@
 					    android:layout_width="300dp" 
 					    android:layout_height="110dp"
 					    
-					    android:text="Blah blah blah blah blah"
+					    android:text="@string/cd_blah"
 					    android:textSize = "15sp"/>
 				</TableRow>
 			</TableLayout>	
diff --git a/res/layout/diary.xml b/res/layout/diary.xml
index d213a2d..c97cd6d 100644
--- a/res/layout/diary.xml
+++ b/res/layout/diary.xml
@@ -34,7 +34,7 @@
 				    android:scrollbars="vertical"
 				    android:layout_height="wrap_content"
 				    android:clickable="true"
-				    android:text="Blah blah blah blah blah." 
+				    android:text="@string/cd_blah"
 				    android:paddingLeft="15dp" 
 				    android:paddingRight="15dp" 
 				    android:paddingTop="15dp"/>
diff --git a/res/layout/game_over.xml b/res/layout/game_over.xml
index d152a62..2bd75b4 100644
--- a/res/layout/game_over.xml
+++ b/res/layout/game_over.xml
@@ -18,7 +18,7 @@
 				<view 
 					class = "com.replica.replicaisland.GameOverActivity$IncrementingTextView"
 					android:id ="@+id/pearl_percent" 
-					android:text="100%" 
+					android:text="@string/s100p"
 					android:layout_height="wrap_content" 
 					android:layout_width="fill_parent" 
 					android:textSize="20sp" 
@@ -35,7 +35,7 @@
 				android:layout_width="fill_parent" 
 				android:layout_gravity="right|center_vertical"
 				android:textColor="#000000" android:textStyle="bold" 
-				android:typeface="serif" android:gravity="right" android:text="0" android:textSize="20sp"/>
+				android:typeface="serif" android:gravity="right" android:text="@string/zero" android:textSize="20sp"/>
 		</LinearLayout>
 		<LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content">
 			<TextView android:text="@string/game_results_total_play_time" android:layout_height="wrap_content" android:layout_width="wrap_content" android:textSize="20sp" android:textColor="#000000" android:textStyle="bold" android:typeface="serif"/>
@@ -47,7 +47,7 @@
 				android:textSize="20sp" 
 				android:layout_gravity="right|center_vertical"
 				android:textColor="#000000" android:textStyle="bold" 
-				android:typeface="serif" android:gravity="right" android:text="3:21:34"/>
+				android:typeface="serif" android:gravity="right" android:text="@string/s3_21_34"/>
 		</LinearLayout>
 		<LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content">
 			<TextView android:text="@string/game_results_ending" android:layout_height="wrap_content" android:layout_width="wrap_content" android:textSize="20sp" android:textColor="#000000" android:textStyle="bold" android:typeface="serif"/>
diff --git a/res/layout/key_config.xml b/res/layout/key_config.xml
index 23d2579..5235df2 100644
--- a/res/layout/key_config.xml
+++ b/res/layout/key_config.xml
@@ -30,7 +30,7 @@
 			    		<TextView android:layout_height="wrap_content" 
 			    			android:layout_gravity="center" 
 			    			android:layout_width="wrap_content" 
-			    			android:text="A"
+                            android:text="@string/k_A"
 			    			android:id="@+id/key_left"/>
 			   		</LinearLayout>
 			   	</LinearLayout>
@@ -51,7 +51,7 @@
 			    		<TextView android:layout_height="wrap_content" 
 			    				android:layout_gravity="center" 
 			    				android:layout_width="wrap_content" 
-			    				android:text="A"
+                                android:text="@string/k_A"
 			    				android:id="@+id/key_right"/>
 			   		</LinearLayout>
 				</LinearLayout>
@@ -75,7 +75,7 @@
 			    		<TextView android:layout_height="wrap_content" 
 			    				android:layout_gravity="center" 
 			    				android:layout_width="wrap_content" 
-			    				android:text="A"
+                                android:text="@string/k_A"
 			    				android:id="@+id/key_jump"/>
 			   		</LinearLayout>
 			   	</LinearLayout>
@@ -96,7 +96,7 @@
 			    		<TextView android:layout_height="wrap_content" 
 			    				android:layout_gravity="center" 
 			    				android:layout_width="wrap_content" 
-			    				android:text="A"
+                                android:text="@string/k_A"
 			    				android:id="@+id/key_attack"/>
 			   		</LinearLayout>
 			   	</LinearLayout>
diff --git a/res/layout/level_select_completed_row.xml b/res/layout/level_select_completed_row.xml
index 8824463..b4395a0 100755
--- a/res/layout/level_select_completed_row.xml
+++ b/res/layout/level_select_completed_row.xml
@@ -26,7 +26,7 @@
 	    android:gravity = "top"
 	    android:layout_x="20dp" 
         android:layout_y="5dp" 
-       	android:text="Level 1-1"
+        android:text="@string/level_1_1"
 	              />
 	              
 	<TextView android:id="@+id/time" xmlns:android="http://schemas.android.com/apk/res/android"
@@ -37,7 +37,7 @@
 	    android:gravity = "top"
 	    android:layout_x="290dp" 
         android:layout_y="25dp" 
-       	android:text="+ 0:02:031"
+        android:text="@string/s0_02_031"
 	              />
 	              
 </AbsoluteLayout>
\ No newline at end of file
diff --git a/res/layout/level_select_disabled_row.xml b/res/layout/level_select_disabled_row.xml
index 65c06ed..a570d4c 100755
--- a/res/layout/level_select_disabled_row.xml
+++ b/res/layout/level_select_disabled_row.xml
@@ -26,7 +26,7 @@
 	    android:gravity = "top"
 	    android:layout_x="20dp" 
         android:layout_y="5dp" 
-       	android:text="Level 1-1"
+        android:text="@string/level_1_1"
 	              />
 	              
 	<TextView android:id="@+id/time" xmlns:android="http://schemas.android.com/apk/res/android"
@@ -37,7 +37,7 @@
 	    android:gravity = "top"
 	    android:layout_x="290dp" 
         android:layout_y="25dp" 
-       	android:text="+ 0:02:031"
+        android:text="@string/s0_02_031"
 	              />
 	              
 </AbsoluteLayout>
\ No newline at end of file
diff --git a/res/layout/level_select_row.xml b/res/layout/level_select_row.xml
index beb0e9b..97fd936 100755
--- a/res/layout/level_select_row.xml
+++ b/res/layout/level_select_row.xml
@@ -24,7 +24,7 @@
 	    android:gravity = "top"
 	    android:layout_x="20dp" 
         android:layout_y="5dp" 
-       	android:text="Level 1-1"
+        android:text="@string/level_1_1"
 	              />
 	              
 	<TextView android:id="@+id/time" xmlns:android="http://schemas.android.com/apk/res/android"
@@ -35,7 +35,7 @@
 	    android:gravity = "top"
 	    android:layout_x="290dp" 
         android:layout_y="25dp" 
-       	android:text="+ 0:02:031"
+        android:text="@string/s0_02_031"
 	              />
 	              
 </AbsoluteLayout>
\ No newline at end of file
diff --git a/res/layout/main.xml b/res/layout/main.xml
index 884e864..3ce28b2 100644
--- a/res/layout/main.xml
+++ b/res/layout/main.xml
@@ -34,7 +34,7 @@
    			android:layout_width="wrap_content" 
    			android:textColor="#FFFFFF" 
    			android:layout_gravity="center_horizontal" 
-   			android:text="Memory #001" android:textSize="20sp" 
+            android:text="@string/level_0_2_lab" android:textSize="20sp"
    			android:textStyle="bold" 
    			android:typeface="sans"/>
    </LinearLayout>
diff --git a/res/layout/slider_preference.xml b/res/layout/slider_preference.xml
index f35c896..2224999 100644
--- a/res/layout/slider_preference.xml
+++ b/res/layout/slider_preference.xml
@@ -6,7 +6,7 @@
 	android:layout_gravity="center_horizontal">
 	<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
 		android:id="@+id/min"
-		android:text="Min" 
+		android:text="@string/min"
 		android:layout_width="wrap_content" 
 		android:layout_height="wrap_content" 
 		android:layout_gravity="center_vertical"/>
@@ -18,7 +18,7 @@
 		android:layout_margin="10dp"/>
 	<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
 		android:id="@+id/max"
-		android:text="Max"
+		android:text="@string/max"
 		android:layout_height="wrap_content"
 		android:layout_width="wrap_content"
 		android:layout_gravity="center_vertical"/>
diff --git a/res/raw/level_0_1_sewer_repeat.bin b/res/raw/level_0_1_sewer_repeat.bin
new file mode 100644
index 0000000..c197f6f
--- /dev/null
+++ b/res/raw/level_0_1_sewer_repeat.bin
Binary files differ
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bfb8417..ee2229b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -413,4 +413,14 @@
 
 <string name="KyleDeadNote">It’s Kyle.  He’s not moving.</string>
 
+<string name="cd_Wanda">Wanda</string>
+<string name="cd_blah">Blah blah blah blah blah</string>
+<string name="s100p">100%</string>
+<string name="zero">0</string>
+<string name="s3_21_34">3:21:34</string>
+<string name="k_A">A</string>
+<string name="level_1_1">Level 1-1</string>
+<string name="s0_02_031">+ 0:02:031</string>
+<string name="min">Min</string>
+<string name="max">Max</string>
 </resources>
diff --git a/res/xml/level_tree.xml b/res/xml/level_tree.xml
index 437cf73..c5f7413 100644
--- a/res/xml/level_tree.xml
+++ b/res/xml/level_tree.xml
@@ -3,7 +3,7 @@
 <levelTree>
 
 <group>
-	<level resource="@raw/level_0_1_sewer" title = "@string/level_0_1_sewer" time = "@string/level_0_1_time" waitmessage="true">
+	<level resource="@raw/level_0_1_sewer_repeat" title = "@string/level_0_1_sewer" time = "@string/level_0_1_time" waitmessage="true">
 		<dialog>
 			<character1 resource ="@xml/level_0_1_dialog_wanda"/>
 		</dialog>
diff --git a/src/com/replica/replicaisland/AndouKun.java b/src/com/replica/replicaisland/AndouKun.java
index 23ca928..8914d1b 100644
--- a/src/com/replica/replicaisland/AndouKun.java
+++ b/src/com/replica/replicaisland/AndouKun.java
@@ -142,18 +142,17 @@
         
         mPrefsEditor = prefs.edit();
         // Make sure that old game information is cleared when we start a new game.
-        if (getIntent().getBooleanExtra("newGame", false)) {
-        	mPrefsEditor.remove(PreferenceConstants.PREFERENCE_LEVEL_ROW);
-        	mPrefsEditor.remove(PreferenceConstants.PREFERENCE_LEVEL_INDEX);
-        	mPrefsEditor.remove(PreferenceConstants.PREFERENCE_LEVEL_COMPLETED);
-        	mPrefsEditor.remove(PreferenceConstants.PREFERENCE_LINEAR_MODE);
-        	mPrefsEditor.remove(PreferenceConstants.PREFERENCE_TOTAL_GAME_TIME);
-        	mPrefsEditor.remove(PreferenceConstants.PREFERENCE_PEARLS_COLLECTED);
-        	mPrefsEditor.remove(PreferenceConstants.PREFERENCE_PEARLS_TOTAL);
-        	mPrefsEditor.remove(PreferenceConstants.PREFERENCE_ROBOTS_DESTROYED);
-			mPrefsEditor.remove(PreferenceConstants.PREFERENCE_DIFFICULTY);
-			mPrefsEditor.commit();
-        }
+        // CTS: clear settings to force start from beginning
+        mPrefsEditor.remove(PreferenceConstants.PREFERENCE_LEVEL_ROW);
+        mPrefsEditor.remove(PreferenceConstants.PREFERENCE_LEVEL_INDEX);
+        mPrefsEditor.remove(PreferenceConstants.PREFERENCE_LEVEL_COMPLETED);
+        mPrefsEditor.remove(PreferenceConstants.PREFERENCE_LINEAR_MODE);
+        mPrefsEditor.remove(PreferenceConstants.PREFERENCE_TOTAL_GAME_TIME);
+        mPrefsEditor.remove(PreferenceConstants.PREFERENCE_PEARLS_COLLECTED);
+        mPrefsEditor.remove(PreferenceConstants.PREFERENCE_PEARLS_TOTAL);
+        mPrefsEditor.remove(PreferenceConstants.PREFERENCE_ROBOTS_DESTROYED);
+        mPrefsEditor.remove(PreferenceConstants.PREFERENCE_DIFFICULTY);
+        mPrefsEditor.commit();
         
         
         mLevelRow = prefs.getInt(PreferenceConstants.PREFERENCE_LEVEL_ROW, 0);
diff --git a/src/com/replica/replicaisland/GLSurfaceView.java b/src/com/replica/replicaisland/GLSurfaceView.java
index 2eb5737..e2dd46a 100644
--- a/src/com/replica/replicaisland/GLSurfaceView.java
+++ b/src/com/replica/replicaisland/GLSurfaceView.java
@@ -479,6 +479,7 @@
      * Must not be called before a renderer has been set.
      */
     public void onPause() {
+        mWatchDog.stop();
         mGLThread.onPause();
     }
 
@@ -490,6 +491,7 @@
      * Must not be called before a renderer has been set.
      */
     public void onResume() {
+        mWatchDog.start();
         mGLThread.onResume();
     }
     
@@ -1281,11 +1283,9 @@
 	                    // Thus, in "safe mode," I force two swaps to occur before 
 	                    // issuing any GL commands.  Don't ask me how long it took
 	                    // to figure this out.
-	                    if (framesSinceResetHack > 1 || !mSafeMode) {
-	                    	mRenderer.onDrawFrame(gl);
-	                    } else {
-	                    	DebugLog.w("GLThread", "Safe Mode Wait...");
-	                    }
+                        // CTS: do not use safe mode.
+                        mWatchDog.reset();
+                        mRenderer.onDrawFrame(gl);
 	                    
 	                    framesSinceResetHack++;
 
@@ -1661,6 +1661,6 @@
     private GLWrapper mGLWrapper;
     private int mDebugFlags;
     private int mEGLContextClientVersion;
-
+    private final RenderingWatchDog mWatchDog = new RenderingWatchDog();
 	
 }
diff --git a/src/com/replica/replicaisland/Grid.java b/src/com/replica/replicaisland/Grid.java
index 783c1bb..f6182e4 100644
--- a/src/com/replica/replicaisland/Grid.java
+++ b/src/com/replica/replicaisland/Grid.java
@@ -26,6 +26,8 @@
 import javax.microedition.khronos.opengles.GL10;
 import javax.microedition.khronos.opengles.GL11;
 
+import android.util.Log;
+
 /**
  * A 2D rectangular mesh. Can be drawn textured or untextured.
  * This version is modified from the original Grid.java (found in
@@ -341,7 +343,10 @@
                 gl11.glGenBuffers(1, buffer, 0);
                 mVertBufferIndex = buffer[0];
                 gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertBufferIndex);
-                final int vertexSize = mVertexBuffer.capacity() * mCoordinateSize; 
+                final int vertexSize = mVertexBuffer.capacity() * mCoordinateSize;
+                // too fast task switching leaves buffers in the middle pos which
+                // crashes app
+                mVertexBuffer.position(0);
                 gl11.glBufferData(GL11.GL_ARRAY_BUFFER, vertexSize, 
                         mVertexBuffer, GL11.GL_STATIC_DRAW);
                 
@@ -352,6 +357,7 @@
                         mTextureCoordBufferIndex);
                 final int texCoordSize = 
                     mTexCoordBuffer.capacity() * mCoordinateSize;
+                mTexCoordBuffer.position(0);
                 gl11.glBufferData(GL11.GL_ARRAY_BUFFER, texCoordSize, 
                         mTexCoordBuffer, GL11.GL_STATIC_DRAW);    
                 
@@ -365,6 +371,8 @@
                         mIndexBufferIndex);
                 // A char is 2 bytes.
                 final int indexSize = mIndexBuffer.capacity() * 2;
+
+                mIndexBuffer.position(0);
                 gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, indexSize, mIndexBuffer, 
                         GL11.GL_STATIC_DRAW);
                 
diff --git a/src/com/replica/replicaisland/RenderingWatchDog.java b/src/com/replica/replicaisland/RenderingWatchDog.java
new file mode 100644
index 0000000..6a77750
--- /dev/null
+++ b/src/com/replica/replicaisland/RenderingWatchDog.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2012 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.replica.replicaisland;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import android.util.Log;
+
+import junit.framework.Assert;
+
+/**
+ * class for checking if rendering function is alive or not.
+ * panic if watch-dog is not reset over certain amount of time
+ */
+public class RenderingWatchDog implements Runnable {
+    /** panic if watch-dog is not reset over this amount of time */
+    private static final long DEFAULT_TIMEOUT_IN_MSECS = 10 * 1000;
+    private static final String TAG = "RenderingWatchDog";
+    private Thread mThread;
+    private Semaphore mSemaphore;
+    private volatile boolean mStopRequested;
+    private final long mTimeoutInMilliSecs;
+
+    public RenderingWatchDog() {
+        this(DEFAULT_TIMEOUT_IN_MSECS);
+    }
+
+    public RenderingWatchDog(long timeoutInMilliSecs) {
+        mTimeoutInMilliSecs = timeoutInMilliSecs;
+    }
+
+    /** start watch-dog */
+    public void start() {
+        Log.i(TAG, "start");
+        mStopRequested = false;
+        mSemaphore = new Semaphore(0);
+        mThread = new Thread(this);
+        mThread.start();
+    }
+
+    /** stop watch-dog */
+    public void stop() {
+        Log.i(TAG, "stop");
+        if (mThread == null) {
+            return; // already finished
+        }
+        mStopRequested = true;
+        mSemaphore.release();
+        try {
+            mThread.join();
+        } catch (InterruptedException e) {
+            // ignore
+        }
+        mThread = null;
+        mSemaphore = null;
+    }
+
+    /** resets watch-dog, thus prevent it from panic */
+    public void reset() {
+        if (!mStopRequested) { // stop requested, but rendering still on-going
+            mSemaphore.release();
+        }
+    }
+
+    @Override
+    public void run() {
+        while (!mStopRequested) {
+            try {
+                Assert.assertTrue("Watchdog timed-out",
+                        mSemaphore.tryAcquire(mTimeoutInMilliSecs, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+                // this thread will not be interrupted,
+                // but if it happens, just check the exit condition.
+            }
+        }
+    }
+}