1 // Copyright 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.ui.base; 6 7 import android.annotation.SuppressLint; 8 import android.app.Activity; 9 import android.app.PendingIntent; 10 import android.content.ContentResolver; 11 import android.content.Context; 12 import android.content.Intent; 13 import android.os.Bundle; 14 import android.util.Log; 15 import android.util.SparseArray; 16 import android.widget.Toast; 17 18 import org.chromium.base.CalledByNative; 19 import org.chromium.base.JNINamespace; 20 import org.chromium.ui.VSyncMonitor; 21 22 import java.lang.ref.WeakReference; 23 import java.util.HashMap; 24 25 /** 26 * The window base class that has the minimum functionality. 27 */ 28 @JNINamespace("ui") 29 public class WindowAndroid { 30 private static final String TAG = "WindowAndroid"; 31 32 // Native pointer to the c++ WindowAndroid object. 33 private long mNativeWindowAndroid = 0; 34 private final VSyncMonitor mVSyncMonitor; 35 36 // A string used as a key to store intent errors in a bundle 37 static final String WINDOW_CALLBACK_ERRORS = "window_callback_errors"; 38 39 // Error code returned when an Intent fails to start an Activity. 40 public static final int START_INTENT_FAILURE = -1; 41 42 protected Context mApplicationContext; 43 protected SparseArray<IntentCallback> mOutstandingIntents; 44 45 // Ideally, this would be a SparseArray<String>, but there's no easy way to store a 46 // SparseArray<String> in a bundle during saveInstanceState(). So we use a HashMap and suppress 47 // the Android lint warning "UseSparseArrays". 48 protected HashMap<Integer, String> mIntentErrors; 49 50 private final VSyncMonitor.Listener mVSyncListener = new VSyncMonitor.Listener() { 51 @Override 52 public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) { 53 if (mNativeWindowAndroid != 0) { 54 nativeOnVSync(mNativeWindowAndroid, vsyncTimeMicros); 55 } 56 } 57 }; 58 59 /** 60 * @return true if onVSync handler is executing. 61 * @see org.chromium.ui.VSyncMonitor#isInsideVSync(). 62 */ isInsideVSync()63 public boolean isInsideVSync() { 64 return mVSyncMonitor.isInsideVSync(); 65 } 66 67 /** 68 * @param context The application context. 69 */ 70 @SuppressLint("UseSparseArrays") WindowAndroid(Context context)71 public WindowAndroid(Context context) { 72 assert context == context.getApplicationContext(); 73 mApplicationContext = context; 74 mOutstandingIntents = new SparseArray<IntentCallback>(); 75 mIntentErrors = new HashMap<Integer, String>(); 76 mVSyncMonitor = new VSyncMonitor(context, mVSyncListener); 77 } 78 79 /** 80 * Shows an intent and returns the results to the callback object. 81 * @param intent The PendingIntent that needs to be shown. 82 * @param callback The object that will receive the results for the intent. 83 * @param errorId The ID of error string to be show if activity is paused before intent 84 * results. 85 * @return Whether the intent was shown. 86 */ showIntent(PendingIntent intent, IntentCallback callback, int errorId)87 public boolean showIntent(PendingIntent intent, IntentCallback callback, int errorId) { 88 return showCancelableIntent(intent, callback, errorId) >= 0; 89 } 90 91 /** 92 * Shows an intent and returns the results to the callback object. 93 * @param intent The intent that needs to be shown. 94 * @param callback The object that will receive the results for the intent. 95 * @param errorId The ID of error string to be show if activity is paused before intent 96 * results. 97 * @return Whether the intent was shown. 98 */ showIntent(Intent intent, IntentCallback callback, int errorId)99 public boolean showIntent(Intent intent, IntentCallback callback, int errorId) { 100 return showCancelableIntent(intent, callback, errorId) >= 0; 101 } 102 103 /** 104 * Shows an intent that could be canceled and returns the results to the callback object. 105 * @param intent The PendingIntent that needs to be shown. 106 * @param callback The object that will receive the results for the intent. 107 * @param errorId The ID of error string to be show if activity is paused before intent 108 * results. 109 * @return A non-negative request code that could be used for finishActivity, or 110 * START_INTENT_FAILURE if failed. 111 */ showCancelableIntent(PendingIntent intent, IntentCallback callback, int errorId)112 public int showCancelableIntent(PendingIntent intent, IntentCallback callback, int errorId) { 113 Log.d(TAG, "Can't show intent as context is not an Activity: " + intent); 114 return START_INTENT_FAILURE; 115 } 116 117 /** 118 * Shows an intent that could be canceled and returns the results to the callback object. 119 * @param intent The intent that needs to be showed. 120 * @param callback The object that will receive the results for the intent. 121 * @param errorId The ID of error string to be show if activity is paused before intent 122 * results. 123 * @return A non-negative request code that could be used for finishActivity, or 124 * START_INTENT_FAILURE if failed. 125 */ showCancelableIntent(Intent intent, IntentCallback callback, int errorId)126 public int showCancelableIntent(Intent intent, IntentCallback callback, int errorId) { 127 Log.d(TAG, "Can't show intent as context is not an Activity: " + intent); 128 return START_INTENT_FAILURE; 129 } 130 131 /** 132 * Force finish another activity that you had previously started with showCancelableIntent. 133 * @param requestCode The request code returned from showCancelableIntent. 134 */ cancelIntent(int requestCode)135 public void cancelIntent(int requestCode) { 136 Log.d(TAG, "Can't cancel intent as context is not an Activity: " + requestCode); 137 } 138 139 /** 140 * Removes a callback from the list of pending intents, so that nothing happens if/when the 141 * result for that intent is received. 142 * @param callback The object that should have received the results 143 * @return True if the callback was removed, false if it was not found. 144 */ removeIntentCallback(IntentCallback callback)145 public boolean removeIntentCallback(IntentCallback callback) { 146 int requestCode = mOutstandingIntents.indexOfValue(callback); 147 if (requestCode < 0) return false; 148 mOutstandingIntents.remove(requestCode); 149 mIntentErrors.remove(requestCode); 150 return true; 151 } 152 153 /** 154 * Displays an error message with a provided error message string. 155 * @param error The error message string to be displayed. 156 */ showError(String error)157 public void showError(String error) { 158 if (error != null) { 159 Toast.makeText(mApplicationContext, error, Toast.LENGTH_SHORT).show(); 160 } 161 } 162 163 /** 164 * Displays an error message from the given resource id. 165 * @param resId The error message string's resource id. 166 */ showError(int resId)167 public void showError(int resId) { 168 showError(mApplicationContext.getString(resId)); 169 } 170 171 /** 172 * Displays an error message for a nonexistent callback. 173 * @param error The error message string to be displayed. 174 */ showCallbackNonExistentError(String error)175 protected void showCallbackNonExistentError(String error) { 176 showError(error); 177 } 178 179 /** 180 * Broadcasts the given intent to all interested BroadcastReceivers. 181 */ sendBroadcast(Intent intent)182 public void sendBroadcast(Intent intent) { 183 mApplicationContext.sendBroadcast(intent); 184 } 185 186 /** 187 * @return A reference to owning Activity. The returned WeakReference will never be null, but 188 * the contained Activity can be null (either if it has been garbage collected or if 189 * this is in the context of a WebView that was not created using an Activity). 190 */ getActivity()191 public WeakReference<Activity> getActivity() { 192 return new WeakReference<Activity>(null); 193 } 194 195 /** 196 * @return The application context for this activity. 197 */ getApplicationContext()198 public Context getApplicationContext() { 199 return mApplicationContext; 200 } 201 202 /** 203 * Saves the error messages that should be shown if any pending intents would return 204 * after the application has been put onPause. 205 * @param bundle The bundle to save the information in onPause 206 */ saveInstanceState(Bundle bundle)207 public void saveInstanceState(Bundle bundle) { 208 bundle.putSerializable(WINDOW_CALLBACK_ERRORS, mIntentErrors); 209 } 210 211 /** 212 * Restores the error messages that should be shown if any pending intents would return 213 * after the application has been put onPause. 214 * @param bundle The bundle to restore the information from onResume 215 */ restoreInstanceState(Bundle bundle)216 public void restoreInstanceState(Bundle bundle) { 217 if (bundle == null) return; 218 219 Object errors = bundle.getSerializable(WINDOW_CALLBACK_ERRORS); 220 if (errors instanceof HashMap) { 221 @SuppressWarnings("unchecked") 222 HashMap<Integer, String> intentErrors = (HashMap<Integer, String>) errors; 223 mIntentErrors = intentErrors; 224 } 225 } 226 227 /** 228 * Responds to the intent result if the intent was created by the native window. 229 * @param requestCode Request code of the requested intent. 230 * @param resultCode Result code of the requested intent. 231 * @param data The data returned by the intent. 232 * @return Boolean value of whether the intent was started by the native window. 233 */ onActivityResult(int requestCode, int resultCode, Intent data)234 public boolean onActivityResult(int requestCode, int resultCode, Intent data) { 235 return false; 236 } 237 238 @CalledByNative requestVSyncUpdate()239 private void requestVSyncUpdate() { 240 mVSyncMonitor.requestUpdate(); 241 } 242 243 /** 244 * An interface that intent callback objects have to implement. 245 */ 246 public interface IntentCallback { 247 /** 248 * Handles the data returned by the requested intent. 249 * @param window A window reference. 250 * @param resultCode Result code of the requested intent. 251 * @param contentResolver An instance of ContentResolver class for accessing returned data. 252 * @param data The data returned by the intent. 253 */ onIntentCompleted(WindowAndroid window, int resultCode, ContentResolver contentResolver, Intent data)254 void onIntentCompleted(WindowAndroid window, int resultCode, 255 ContentResolver contentResolver, Intent data); 256 } 257 258 /** 259 * Tests that an activity is available to handle the passed in intent. 260 * @param intent The intent to check. 261 * @return True if an activity is available to process this intent when started, meaning that 262 * Context.startActivity will not throw ActivityNotFoundException. 263 */ canResolveActivity(Intent intent)264 public boolean canResolveActivity(Intent intent) { 265 return mApplicationContext.getPackageManager().resolveActivity(intent, 0) != null; 266 } 267 268 /** 269 * Destroys the c++ WindowAndroid object if one has been created. 270 */ destroy()271 public void destroy() { 272 if (mNativeWindowAndroid != 0) { 273 nativeDestroy(mNativeWindowAndroid); 274 mNativeWindowAndroid = 0; 275 } 276 } 277 278 /** 279 * Returns a pointer to the c++ AndroidWindow object and calls the initializer if 280 * the object has not been previously initialized. 281 * @return A pointer to the c++ AndroidWindow. 282 */ getNativePointer()283 public long getNativePointer() { 284 if (mNativeWindowAndroid == 0) { 285 mNativeWindowAndroid = nativeInit(mVSyncMonitor.getVSyncPeriodInMicroseconds()); 286 } 287 return mNativeWindowAndroid; 288 } 289 nativeInit(long vsyncPeriod)290 private native long nativeInit(long vsyncPeriod); nativeOnVSync(long nativeWindowAndroid, long vsyncTimeMicros)291 private native void nativeOnVSync(long nativeWindowAndroid, long vsyncTimeMicros); nativeDestroy(long nativeWindowAndroid)292 private native void nativeDestroy(long nativeWindowAndroid); 293 294 } 295