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 * @param context The application context. 61 */ 62 @SuppressLint("UseSparseArrays") 63 public WindowAndroid(Context context) { 64 assert context == context.getApplicationContext(); 65 mApplicationContext = context; 66 mOutstandingIntents = new SparseArray<IntentCallback>(); 67 mIntentErrors = new HashMap<Integer, String>(); 68 mVSyncMonitor = new VSyncMonitor(context, mVSyncListener); 69 } 70 71 /** 72 * Shows an intent and returns the results to the callback object. 73 * @param intent The PendingIntent that needs to be shown. 74 * @param callback The object that will receive the results for the intent. 75 * @param errorId The ID of error string to be show if activity is paused before intent 76 * results. 77 * @return Whether the intent was shown. 78 */ 79 public boolean showIntent(PendingIntent intent, IntentCallback callback, int errorId) { 80 return showCancelableIntent(intent, callback, errorId) >= 0; 81 } 82 83 /** 84 * Shows an intent and returns the results to the callback object. 85 * @param intent The intent that needs to be shown. 86 * @param callback The object that will receive the results for the intent. 87 * @param errorId The ID of error string to be show if activity is paused before intent 88 * results. 89 * @return Whether the intent was shown. 90 */ 91 public boolean showIntent(Intent intent, IntentCallback callback, int errorId) { 92 return showCancelableIntent(intent, callback, errorId) >= 0; 93 } 94 95 /** 96 * Shows an intent that could be canceled and returns the results to the callback object. 97 * @param intent The PendingIntent that needs to be shown. 98 * @param callback The object that will receive the results for the intent. 99 * @param errorId The ID of error string to be show if activity is paused before intent 100 * results. 101 * @return A non-negative request code that could be used for finishActivity, or 102 * START_INTENT_FAILURE if failed. 103 */ 104 public int showCancelableIntent(PendingIntent intent, IntentCallback callback, int errorId) { 105 Log.d(TAG, "Can't show intent as context is not an Activity: " + intent); 106 return START_INTENT_FAILURE; 107 } 108 109 /** 110 * Shows an intent that could be canceled and returns the results to the callback object. 111 * @param intent The intent that needs to be showed. 112 * @param callback The object that will receive the results for the intent. 113 * @param errorId The ID of error string to be show if activity is paused before intent 114 * results. 115 * @return A non-negative request code that could be used for finishActivity, or 116 * START_INTENT_FAILURE if failed. 117 */ 118 public int showCancelableIntent(Intent intent, IntentCallback callback, int errorId) { 119 Log.d(TAG, "Can't show intent as context is not an Activity: " + intent); 120 return START_INTENT_FAILURE; 121 } 122 123 /** 124 * Force finish another activity that you had previously started with showCancelableIntent. 125 * @param requestCode The request code returned from showCancelableIntent. 126 */ 127 public void cancelIntent(int requestCode) { 128 Log.d(TAG, "Can't cancel intent as context is not an Activity: " + requestCode); 129 } 130 131 /** 132 * Removes a callback from the list of pending intents, so that nothing happens if/when the 133 * result for that intent is received. 134 * @param callback The object that should have received the results 135 * @return True if the callback was removed, false if it was not found. 136 */ 137 public boolean removeIntentCallback(IntentCallback callback) { 138 int requestCode = mOutstandingIntents.indexOfValue(callback); 139 if (requestCode < 0) return false; 140 mOutstandingIntents.remove(requestCode); 141 mIntentErrors.remove(requestCode); 142 return true; 143 } 144 145 /** 146 * Displays an error message with a provided error message string. 147 * @param error The error message string to be displayed. 148 */ 149 public void showError(String error) { 150 if (error != null) { 151 Toast.makeText(mApplicationContext, error, Toast.LENGTH_SHORT).show(); 152 } 153 } 154 155 /** 156 * Displays an error message from the given resource id. 157 * @param resId The error message string's resource id. 158 */ 159 public void showError(int resId) { 160 showError(mApplicationContext.getString(resId)); 161 } 162 163 /** 164 * Displays an error message for a nonexistent callback. 165 * @param error The error message string to be displayed. 166 */ 167 protected void showCallbackNonExistentError(String error) { 168 showError(error); 169 } 170 171 /** 172 * Broadcasts the given intent to all interested BroadcastReceivers. 173 */ 174 public void sendBroadcast(Intent intent) { 175 mApplicationContext.sendBroadcast(intent); 176 } 177 178 /** 179 * @return A reference to owning Activity. The returned WeakReference will never be null, but 180 * the contained Activity can be null (either if it has been garbage collected or if 181 * this is in the context of a WebView that was not created using an Activity). 182 */ 183 public WeakReference<Activity> getActivity() { 184 return new WeakReference<Activity>(null); 185 } 186 187 /** 188 * @return The application context for this activity. 189 */ 190 public Context getApplicationContext() { 191 return mApplicationContext; 192 } 193 194 /** 195 * Saves the error messages that should be shown if any pending intents would return 196 * after the application has been put onPause. 197 * @param bundle The bundle to save the information in onPause 198 */ 199 public void saveInstanceState(Bundle bundle) { 200 bundle.putSerializable(WINDOW_CALLBACK_ERRORS, mIntentErrors); 201 } 202 203 /** 204 * Restores the error messages that should be shown if any pending intents would return 205 * after the application has been put onPause. 206 * @param bundle The bundle to restore the information from onResume 207 */ 208 public void restoreInstanceState(Bundle bundle) { 209 if (bundle == null) return; 210 211 Object errors = bundle.getSerializable(WINDOW_CALLBACK_ERRORS); 212 if (errors instanceof HashMap) { 213 @SuppressWarnings("unchecked") 214 HashMap<Integer, String> intentErrors = (HashMap<Integer, String>) errors; 215 mIntentErrors = intentErrors; 216 } 217 } 218 219 /** 220 * Responds to the intent result if the intent was created by the native window. 221 * @param requestCode Request code of the requested intent. 222 * @param resultCode Result code of the requested intent. 223 * @param data The data returned by the intent. 224 * @return Boolean value of whether the intent was started by the native window. 225 */ 226 public boolean onActivityResult(int requestCode, int resultCode, Intent data) { 227 return false; 228 } 229 230 @CalledByNative 231 private void requestVSyncUpdate() { 232 mVSyncMonitor.requestUpdate(); 233 } 234 235 /** 236 * An interface that intent callback objects have to implement. 237 */ 238 public interface IntentCallback { 239 /** 240 * Handles the data returned by the requested intent. 241 * @param window A window reference. 242 * @param resultCode Result code of the requested intent. 243 * @param contentResolver An instance of ContentResolver class for accessing returned data. 244 * @param data The data returned by the intent. 245 */ 246 void onIntentCompleted(WindowAndroid window, int resultCode, 247 ContentResolver contentResolver, Intent data); 248 } 249 250 /** 251 * Tests that an activity is available to handle the passed in intent. 252 * @param intent The intent to check. 253 * @return True if an activity is available to process this intent when started, meaning that 254 * Context.startActivity will not throw ActivityNotFoundException. 255 */ 256 public boolean canResolveActivity(Intent intent) { 257 return mApplicationContext.getPackageManager().resolveActivity(intent, 0) != null; 258 } 259 260 /** 261 * Destroys the c++ WindowAndroid object if one has been created. 262 */ 263 public void destroy() { 264 if (mNativeWindowAndroid != 0) { 265 nativeDestroy(mNativeWindowAndroid); 266 mNativeWindowAndroid = 0; 267 } 268 } 269 270 /** 271 * Returns a pointer to the c++ AndroidWindow object and calls the initializer if 272 * the object has not been previously initialized. 273 * @return A pointer to the c++ AndroidWindow. 274 */ 275 public long getNativePointer() { 276 if (mNativeWindowAndroid == 0) { 277 mNativeWindowAndroid = nativeInit(mVSyncMonitor.getVSyncPeriodInMicroseconds()); 278 } 279 return mNativeWindowAndroid; 280 } 281 282 private native long nativeInit(long vsyncPeriod); 283 private native void nativeOnVSync(long nativeWindowAndroid, long vsyncTimeMicros); 284 private native void nativeDestroy(long nativeWindowAndroid); 285 286 } 287