Tracer build fixes. (b=588021, r=dvander)
[mozilla-central.git] / embedding / android / GeckoAppShell.java
blobe8856c18537184fbd33381012111210693b6478b
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2 * ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Android code.
17 * The Initial Developer of the Original Code is Mozilla Foundation.
18 * Portions created by the Initial Developer are Copyright (C) 2010
19 * the Initial Developer. All Rights Reserved.
21 * Contributor(s):
22 * Vladimir Vukicevic <vladimir@pobox.com>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 package org.mozilla.gecko;
40 import java.io.*;
41 import java.util.*;
42 import java.util.zip.*;
43 import java.nio.*;
44 import java.lang.reflect.*;
46 import android.os.*;
47 import android.app.*;
48 import android.text.*;
49 import android.view.*;
50 import android.view.inputmethod.*;
51 import android.content.*;
52 import android.content.res.*;
53 import android.content.pm.*;
54 import android.graphics.*;
55 import android.widget.*;
56 import android.hardware.*;
57 import android.location.*;
59 import android.util.*;
60 import android.net.Uri;
62 class GeckoAppShell
64 static {
65 sGeckoRunning = false;
68 // static members only
69 private GeckoAppShell() { }
71 static boolean sGeckoRunning;
72 static private GeckoEvent gPendingResize = null;
74 static private boolean gRestartScheduled = false;
76 static private final Timer mIMETimer = new Timer();
77 static private final HashMap<Integer, AlertNotification>
78 mAlertNotifications = new HashMap<Integer, AlertNotification>();
80 static private final int NOTIFY_IME_RESETINPUTSTATE = 0;
81 static private final int NOTIFY_IME_SETOPENSTATE = 1;
82 static private final int NOTIFY_IME_SETENABLED = 2;
83 static private final int NOTIFY_IME_CANCELCOMPOSITION = 3;
84 static private final int NOTIFY_IME_FOCUSCHANGE = 4;
86 /* The Android-side API: API methods that Android calls */
88 // Initialization methods
89 public static native void nativeInit();
90 public static native void nativeRun(String args);
92 // helper methods
93 public static native void setSurfaceView(GeckoSurfaceView sv);
94 public static native void putenv(String map);
95 public static native void onResume();
96 public static native void onLowMemory();
97 public static native void callObserver(String observerKey, String topic, String data);
98 public static native void removeObserver(String observerKey);
99 public static native void loadLibs(String apkName);
101 // java-side stuff
102 public static void loadGeckoLibs(String apkName) {
103 // The package data lib directory isn't placed in ld.so's
104 // search path, so we have to manually load libraries that
105 // libxul will depend on. Not ideal.
106 System.loadLibrary("mozutils");
109 Intent i = GeckoApp.mAppContext.getIntent();
110 String env = i.getStringExtra("env0");
111 Log.i("GeckoApp", "env0: "+ env);
112 for (int c = 1; env != null; c++) {
113 GeckoAppShell.putenv(env);
114 env = i.getStringExtra("env" + c);
115 Log.i("GeckoApp", "env"+ c +": "+ env);
118 File f = new File("/data/data/org.mozilla." +
119 GeckoApp.mAppContext.getAppName() +"/tmp");
120 if (!f.exists())
121 f.mkdirs();
123 GeckoAppShell.putenv("TMPDIR=" + f.getPath());
125 f = Environment.getDownloadCacheDirectory();
126 GeckoAppShell.putenv("EXTERNAL_STORAGE" + f.getPath());
128 GeckoAppShell.putenv("LANG=" + Locale.getDefault().toString());
130 loadLibs(apkName);
133 public static void runGecko(String apkPath, String args, String url) {
134 // run gecko -- it will spawn its own thread
135 GeckoAppShell.nativeInit();
137 // Tell Gecko where the target surface view is for rendering
138 GeckoAppShell.setSurfaceView(GeckoApp.surfaceView);
140 // First argument is the .apk path
141 String combinedArgs = apkPath + " -omnijar " + apkPath;
142 if (args != null)
143 combinedArgs += " " + args;
144 if (url != null)
145 combinedArgs += " " + url;
146 // and go
147 GeckoAppShell.nativeRun(combinedArgs);
150 private static GeckoEvent mLastDrawEvent;
152 public static void sendEventToGecko(GeckoEvent e) {
153 if (sGeckoRunning) {
154 if (gPendingResize != null) {
155 notifyGeckoOfEvent(gPendingResize);
156 gPendingResize = null;
158 notifyGeckoOfEvent(e);
159 } else {
160 if (e.mType == GeckoEvent.SIZE_CHANGED)
161 gPendingResize = e;
165 // Tell the Gecko event loop that an event is available.
166 public static native void notifyGeckoOfEvent(GeckoEvent event);
169 * The Gecko-side API: API methods that Gecko calls
171 public static void scheduleRedraw() {
172 // Redraw everything
173 scheduleRedraw(0, -1, -1, -1, -1);
176 public static void scheduleRedraw(int nativeWindow, int x, int y, int w, int h) {
177 GeckoEvent e;
179 if (x == -1) {
180 e = new GeckoEvent(GeckoEvent.DRAW, null);
181 } else {
182 e = new GeckoEvent(GeckoEvent.DRAW, new Rect(x, y, w, h));
185 e.mNativeWindow = nativeWindow;
187 sendEventToGecko(e);
190 /* Delay updating IME states (see bug 573800) */
191 private static final class IMEStateUpdater extends TimerTask
193 static private IMEStateUpdater instance;
194 private boolean mEnable, mReset;
196 static private IMEStateUpdater getInstance() {
197 if (instance == null) {
198 instance = new IMEStateUpdater();
199 mIMETimer.schedule(instance, 200);
201 return instance;
204 static public synchronized void enableIME() {
205 getInstance().mEnable = true;
208 static public synchronized void resetIME() {
209 getInstance().mReset = true;
212 public void run() {
213 synchronized(IMEStateUpdater.class) {
214 instance = null;
217 InputMethodManager imm = (InputMethodManager)
218 GeckoApp.surfaceView.getContext().getSystemService(
219 Context.INPUT_METHOD_SERVICE);
220 if (imm == null)
221 return;
223 if (mReset)
224 imm.restartInput(GeckoApp.surfaceView);
226 if (!mEnable)
227 return;
229 if (GeckoApp.surfaceView.mIMEState !=
230 GeckoSurfaceView.IME_STATE_DISABLED)
231 imm.showSoftInput(GeckoApp.surfaceView, 0);
232 else
233 imm.hideSoftInputFromWindow(
234 GeckoApp.surfaceView.getWindowToken(), 0);
238 public static void notifyIME(int type, int state) {
239 if (GeckoApp.surfaceView == null)
240 return;
242 switch (type) {
243 case NOTIFY_IME_RESETINPUTSTATE:
244 IMEStateUpdater.resetIME();
245 // keep current enabled state
246 IMEStateUpdater.enableIME();
247 break;
249 case NOTIFY_IME_SETENABLED:
250 /* When IME is 'disabled', IME processing is disabled.
251 In addition, the IME UI is hidden */
252 GeckoApp.surfaceView.mIMEState = state;
253 IMEStateUpdater.enableIME();
254 break;
256 case NOTIFY_IME_CANCELCOMPOSITION:
257 IMEStateUpdater.resetIME();
258 break;
260 case NOTIFY_IME_FOCUSCHANGE:
261 GeckoApp.surfaceView.mIMEFocus = state != 0;
262 IMEStateUpdater.resetIME();
263 break;
268 public static void notifyIMEChange(String text, int start, int end, int newEnd) {
269 if (GeckoApp.surfaceView == null ||
270 GeckoApp.surfaceView.inputConnection == null)
271 return;
273 InputMethodManager imm = (InputMethodManager)
274 GeckoApp.surfaceView.getContext().getSystemService(
275 Context.INPUT_METHOD_SERVICE);
276 if (imm == null)
277 return;
279 if (newEnd < 0)
280 GeckoApp.surfaceView.inputConnection.notifySelectionChange(
281 imm, start, end);
282 else
283 GeckoApp.surfaceView.inputConnection.notifyTextChange(
284 imm, text, start, end, newEnd);
287 public static void enableAccelerometer(boolean enable) {
288 SensorManager sm = (SensorManager)
289 GeckoApp.surfaceView.getContext().getSystemService(Context.SENSOR_SERVICE);
291 if (enable) {
292 Sensor accelSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
293 if (accelSensor == null)
294 return;
296 sm.registerListener(GeckoApp.surfaceView, accelSensor, SensorManager.SENSOR_DELAY_GAME);
297 } else {
298 sm.unregisterListener(GeckoApp.surfaceView);
302 public static void enableLocation(boolean enable) {
303 LocationManager lm = (LocationManager)
304 GeckoApp.surfaceView.getContext().getSystemService(Context.LOCATION_SERVICE);
306 if (enable) {
307 Criteria crit = new Criteria();
308 crit.setAccuracy(Criteria.ACCURACY_FINE);
309 String provider = lm.getBestProvider(crit, true);
310 if (provider == null)
311 return;
313 Location loc = lm.getLastKnownLocation(provider);
314 if (loc != null)
315 sendEventToGecko(new GeckoEvent(loc));
316 lm.requestLocationUpdates(provider, 100, (float).5, GeckoApp.surfaceView, Looper.getMainLooper());
317 } else {
318 lm.removeUpdates(GeckoApp.surfaceView);
322 public static void moveTaskToBack() {
323 GeckoApp.mAppContext.moveTaskToBack(true);
326 public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
327 GeckoApp.surfaceView.inputConnection.mSelectionStart = selectionStart;
328 GeckoApp.surfaceView.inputConnection.mSelectionLength = selectionLength;
329 try {
330 GeckoApp.surfaceView.inputConnection.mQueryResult.put(result);
331 } catch (InterruptedException e) {
335 static void onAppShellReady()
337 sGeckoRunning = true;
338 if (gPendingResize != null) {
339 notifyGeckoOfEvent(gPendingResize);
340 gPendingResize = null;
344 static void onXreExit() {
345 sGeckoRunning = false;
346 Log.i("GeckoAppJava", "XRE exited");
347 if (gRestartScheduled) {
348 GeckoApp.mAppContext.doRestart();
349 } else {
350 Log.i("GeckoAppJava", "we're done, good bye");
351 GeckoApp.mAppContext.finish();
352 System.exit(0);
356 static void scheduleRestart() {
357 Log.i("GeckoAppJava", "scheduling restart");
358 gRestartScheduled = true;
361 // "Installs" an application by creating a shortcut
362 static void installWebApplication(String aURI, String aTitle, String aIconData) {
363 Log.w("GeckoAppJava", "installWebApplication for " + aURI + " [" + aTitle + "]");
365 // the intent to be launched by the shortcut
366 Intent shortcutIntent = new Intent("org.mozilla.fennec.WEBAPP");
367 shortcutIntent.setClassName(GeckoApp.mAppContext,
368 "org.mozilla." + GeckoApp.mAppContext.getAppName() + ".App");
369 shortcutIntent.putExtra("args", "--webapp=" + aURI);
371 Intent intent = new Intent();
372 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
373 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
374 byte[] raw = Base64.decode(aIconData.substring(22), Base64.DEFAULT);
375 Bitmap bitmap = BitmapFactory.decodeByteArray(raw, 0, raw.length);
376 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
377 intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
378 GeckoApp.mAppContext.sendBroadcast(intent);
381 static String[] getHandlersForMimeType(String aMimeType, String aAction) {
382 Intent intent = getIntentForActionString(aAction);
383 if (aMimeType != null && aMimeType.length() > 0)
384 intent.setType(aMimeType);
385 return getHandlersForIntent(intent);
388 static String[] getHandlersForProtocol(String aScheme, String aAction) {
389 Intent intent = getIntentForActionString(aAction);
390 Uri uri = new Uri.Builder().scheme(aScheme).build();
391 intent.setData(uri);
392 return getHandlersForIntent(intent);
395 static String[] getHandlersForIntent(Intent intent) {
396 PackageManager pm =
397 GeckoApp.surfaceView.getContext().getPackageManager();
398 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
399 int numAttr = 4;
400 String[] ret = new String[list.size() * numAttr];
401 for (int i = 0; i < list.size(); i++) {
402 ResolveInfo resolveInfo = list.get(i);
403 ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
404 if (resolveInfo.isDefault)
405 ret[i * numAttr + 1] = "default";
406 else
407 ret[i * numAttr + 1] = "";
408 ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
409 ret[i * numAttr + 3] = resolveInfo.activityInfo.name;
411 return ret;
414 static Intent getIntentForActionString(String aAction) {
415 // Default to the view action if no other action as been specified.
416 if (aAction != null && aAction.length() > 0)
417 return new Intent(aAction);
418 else
419 return new Intent(Intent.ACTION_VIEW);
422 static String getMimeTypeFromExtensions(String aFileExt) {
423 android.webkit.MimeTypeMap mtm =
424 android.webkit.MimeTypeMap.getSingleton();
425 StringTokenizer st = new StringTokenizer(aFileExt, "., ");
426 String type = null;
427 String subType = null;
428 while (st.hasMoreElements()) {
429 String ext = st.nextToken();
430 String mt = mtm.getMimeTypeFromExtension(ext);
431 if (mt == null)
432 continue;
433 int slash = mt.indexOf('/');
434 String tmpType = mt.substring(0, slash);
435 if (!tmpType.equalsIgnoreCase(type))
436 type = type == null ? tmpType : "*";
437 String tmpSubType = mt.substring(slash + 1);
438 if (!tmpSubType.equalsIgnoreCase(subType))
439 subType = subType == null ? tmpSubType : "*";
441 if (type == null)
442 type = "*";
443 if (subType == null)
444 subType = "*";
445 return type + "/" + subType;
448 static boolean openUriExternal(String aUriSpec, String aMimeType, String aPackageName,
449 String aClassName, String aAction, String aTitle) {
450 Intent intent = getIntentForActionString(aAction);
451 if (aAction.equalsIgnoreCase(Intent.ACTION_SEND)) {
452 intent.putExtra(Intent.EXTRA_TEXT, aUriSpec);
453 intent.putExtra(Intent.EXTRA_SUBJECT, aTitle);
454 if (aMimeType != null && aMimeType.length() > 0)
455 intent.setType(aMimeType);
456 } else if (aMimeType.length() > 0) {
457 intent.setDataAndType(Uri.parse(aUriSpec), aMimeType);
458 } else {
459 intent.setData(Uri.parse(aUriSpec));
461 if (aPackageName.length() > 0 && aClassName.length() > 0)
462 intent.setClassName(aPackageName, aClassName);
464 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
465 try {
466 GeckoApp.surfaceView.getContext().startActivity(intent);
467 return true;
468 } catch(ActivityNotFoundException e) {
469 return false;
473 static String getClipboardText() {
474 Context context = GeckoApp.surfaceView.getContext();
475 ClipboardManager cm = (ClipboardManager)
476 context.getSystemService(Context.CLIPBOARD_SERVICE);
477 if (!cm.hasText())
478 return null;
479 return cm.getText().toString();
482 static void setClipboardText(String text) {
483 Context context = GeckoApp.surfaceView.getContext();
484 ClipboardManager cm = (ClipboardManager)
485 context.getSystemService(Context.CLIPBOARD_SERVICE);
486 cm.setText(text);
489 public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
490 String aAlertCookie, String aAlertName) {
491 Log.i("GeckoAppJava", "GeckoAppShell.showAlertNotification\n" +
492 "- image = '" + aImageUrl + "'\n" +
493 "- title = '" + aAlertTitle + "'\n" +
494 "- text = '" + aAlertText +"'\n" +
495 "- cookie = '" + aAlertCookie +"'\n" +
496 "- name = '" + aAlertName + "'");
498 int icon = R.drawable.icon; // Just use the app icon by default
500 Uri imageUri = Uri.parse(aImageUrl);
501 String scheme = imageUri.getScheme();
502 if ("drawable".equals(scheme)) {
503 String resource = imageUri.getSchemeSpecificPart();
504 resource = resource.substring(resource.lastIndexOf('/') + 1);
505 try {
506 Class drawableClass = R.drawable.class;
507 Field f = drawableClass.getField(resource);
508 icon = f.getInt(null);
509 } catch (Exception e) {} // just means the resource doesn't exist
512 int notificationID = aAlertName.hashCode();
514 // Remove the old notification with the same ID, if any
515 removeNotification(notificationID);
517 AlertNotification notification = new AlertNotification(GeckoApp.mAppContext,
518 notificationID, icon, aAlertTitle, System.currentTimeMillis());
520 // The intent to launch when the user clicks the expanded notification
521 Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLICK);
522 notificationIntent.setClassName(GeckoApp.mAppContext,
523 "org.mozilla." + GeckoApp.mAppContext.getAppName() + ".NotificationHandler");
525 // Put the strings into the intent as an URI "alert:<name>#<cookie>"
526 Uri dataUri = Uri.fromParts("alert", aAlertName, aAlertCookie);
527 notificationIntent.setData(dataUri);
529 PendingIntent contentIntent = PendingIntent.getActivity(GeckoApp.mAppContext, 0, notificationIntent, 0);
530 notification.setLatestEventInfo(GeckoApp.mAppContext, aAlertTitle, aAlertText, contentIntent);
532 // The intent to execute when the status entry is deleted by the user with the "Clear All Notifications" button
533 Intent clearNotificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLEAR);
534 clearNotificationIntent.setClassName(GeckoApp.mAppContext,
535 "org.mozilla." + GeckoApp.mAppContext.getAppName() + ".NotificationHandler");
536 clearNotificationIntent.setData(dataUri);
538 PendingIntent pendingClearIntent = PendingIntent.getActivity(GeckoApp.mAppContext, 0, clearNotificationIntent, 0);
539 notification.deleteIntent = pendingClearIntent;
541 mAlertNotifications.put(notificationID, notification);
543 notification.show();
545 Log.i("GeckoAppJava", "Created notification ID " + notificationID);
548 public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
549 Log.i("GeckoAppJava", "GeckoAppShell.alertsProgressListener_OnProgress\n" +
550 "- name = '" + aAlertName +"', " +
551 "progress = " + aProgress +" / " + aProgressMax + ", text = '" + aAlertText + "'");
553 int notificationID = aAlertName.hashCode();
554 AlertNotification notification = mAlertNotifications.get(notificationID);
555 if (notification != null)
556 notification.updateProgress(aAlertText, aProgress, aProgressMax);
559 public static void alertsProgressListener_OnCancel(String aAlertName) {
560 Log.i("GeckoAppJava", "GeckoAppShell.alertsProgressListener_OnCancel('" + aAlertName + "'");
562 removeObserver(aAlertName);
564 int notificationID = aAlertName.hashCode();
565 removeNotification(notificationID);
568 public static void handleNotification(String aAction, String aAlertName, String aAlertCookie) {
569 int notificationID = aAlertName.hashCode();
571 if (GeckoApp.ACTION_ALERT_CLICK.equals(aAction)) {
572 Log.i("GeckoAppJava", "GeckoAppShell.handleNotification: callObserver(alertclickcallback)");
573 callObserver(aAlertName, "alertclickcallback", aAlertCookie);
575 AlertNotification notification = mAlertNotifications.get(notificationID);
576 if (notification != null && notification.isProgressStyle()) {
577 // When clicked, keep the notification, if it displays a progress
578 return;
582 callObserver(aAlertName, "alertfinished", aAlertCookie);
584 removeObserver(aAlertName);
586 removeNotification(notificationID);
589 private static void removeNotification(int notificationID) {
590 mAlertNotifications.remove(notificationID);
592 NotificationManager notificationManager = (NotificationManager)
593 GeckoApp.mAppContext.getSystemService(Context.NOTIFICATION_SERVICE);
594 notificationManager.cancel(notificationID);
597 public static int getDpi() {
598 DisplayMetrics metrics = new DisplayMetrics();
599 GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
600 return metrics.densityDpi;
603 public static void setFullScreen(boolean fullscreen) {
604 GeckoApp.mFullscreen = fullscreen;
606 // force a reconfiguration to hide/show the system bar
607 GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
608 GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
609 GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
612 public static String showFilePicker(String aFilters) {
613 return GeckoApp.mAppContext.
614 showFilePicker(getMimeTypeFromExtensions(aFilters));
617 public static void showInputMethodPicker() {
618 InputMethodManager imm = (InputMethodManager) GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
619 imm.showInputMethodPicker();
622 public static void hideProgressDialog() {
623 if (GeckoApp.mAppContext.mProgressDialog != null) {
624 GeckoApp.mAppContext.mProgressDialog.dismiss();
625 GeckoApp.mAppContext.mProgressDialog = null;