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
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.
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
;
42 import java
.util
.zip
.*;
44 import java
.lang
.reflect
.*;
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
;
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
);
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
);
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");
123 GeckoAppShell
.putenv("TMPDIR=" + f
.getPath());
125 f
= Environment
.getDownloadCacheDirectory();
126 GeckoAppShell
.putenv("EXTERNAL_STORAGE" + f
.getPath());
128 GeckoAppShell
.putenv("LANG=" + Locale
.getDefault().toString());
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
;
143 combinedArgs
+= " " + args
;
145 combinedArgs
+= " " + url
;
147 GeckoAppShell
.nativeRun(combinedArgs
);
150 private static GeckoEvent mLastDrawEvent
;
152 public static void sendEventToGecko(GeckoEvent e
) {
154 if (gPendingResize
!= null) {
155 notifyGeckoOfEvent(gPendingResize
);
156 gPendingResize
= null;
158 notifyGeckoOfEvent(e
);
160 if (e
.mType
== GeckoEvent
.SIZE_CHANGED
)
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() {
173 scheduleRedraw(0, -1, -1, -1, -1);
176 public static void scheduleRedraw(int nativeWindow
, int x
, int y
, int w
, int h
) {
180 e
= new GeckoEvent(GeckoEvent
.DRAW
, null);
182 e
= new GeckoEvent(GeckoEvent
.DRAW
, new Rect(x
, y
, w
, h
));
185 e
.mNativeWindow
= nativeWindow
;
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);
204 static public synchronized void enableIME() {
205 getInstance().mEnable
= true;
208 static public synchronized void resetIME() {
209 getInstance().mReset
= true;
213 synchronized(IMEStateUpdater
.class) {
217 InputMethodManager imm
= (InputMethodManager
)
218 GeckoApp
.surfaceView
.getContext().getSystemService(
219 Context
.INPUT_METHOD_SERVICE
);
224 imm
.restartInput(GeckoApp
.surfaceView
);
229 if (GeckoApp
.surfaceView
.mIMEState
!=
230 GeckoSurfaceView
.IME_STATE_DISABLED
)
231 imm
.showSoftInput(GeckoApp
.surfaceView
, 0);
233 imm
.hideSoftInputFromWindow(
234 GeckoApp
.surfaceView
.getWindowToken(), 0);
238 public static void notifyIME(int type
, int state
) {
239 if (GeckoApp
.surfaceView
== null)
243 case NOTIFY_IME_RESETINPUTSTATE
:
244 IMEStateUpdater
.resetIME();
245 // keep current enabled state
246 IMEStateUpdater
.enableIME();
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();
256 case NOTIFY_IME_CANCELCOMPOSITION
:
257 IMEStateUpdater
.resetIME();
260 case NOTIFY_IME_FOCUSCHANGE
:
261 GeckoApp
.surfaceView
.mIMEFocus
= state
!= 0;
262 IMEStateUpdater
.resetIME();
268 public static void notifyIMEChange(String text
, int start
, int end
, int newEnd
) {
269 if (GeckoApp
.surfaceView
== null ||
270 GeckoApp
.surfaceView
.inputConnection
== null)
273 InputMethodManager imm
= (InputMethodManager
)
274 GeckoApp
.surfaceView
.getContext().getSystemService(
275 Context
.INPUT_METHOD_SERVICE
);
280 GeckoApp
.surfaceView
.inputConnection
.notifySelectionChange(
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
);
292 Sensor accelSensor
= sm
.getDefaultSensor(Sensor
.TYPE_ACCELEROMETER
);
293 if (accelSensor
== null)
296 sm
.registerListener(GeckoApp
.surfaceView
, accelSensor
, SensorManager
.SENSOR_DELAY_GAME
);
298 sm
.unregisterListener(GeckoApp
.surfaceView
);
302 public static void enableLocation(boolean enable
) {
303 LocationManager lm
= (LocationManager
)
304 GeckoApp
.surfaceView
.getContext().getSystemService(Context
.LOCATION_SERVICE
);
307 Criteria crit
= new Criteria();
308 crit
.setAccuracy(Criteria
.ACCURACY_FINE
);
309 String provider
= lm
.getBestProvider(crit
, true);
310 if (provider
== null)
313 Location loc
= lm
.getLastKnownLocation(provider
);
315 sendEventToGecko(new GeckoEvent(loc
));
316 lm
.requestLocationUpdates(provider
, 100, (float).5, GeckoApp
.surfaceView
, Looper
.getMainLooper());
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
;
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();
350 Log
.i("GeckoAppJava", "we're done, good bye");
351 GeckoApp
.mAppContext
.finish();
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();
392 return getHandlersForIntent(intent
);
395 static String
[] getHandlersForIntent(Intent intent
) {
397 GeckoApp
.surfaceView
.getContext().getPackageManager();
398 List
<ResolveInfo
> list
= pm
.queryIntentActivities(intent
, 0);
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";
407 ret
[i
* numAttr
+ 1] = "";
408 ret
[i
* numAttr
+ 2] = resolveInfo
.activityInfo
.applicationInfo
.packageName
;
409 ret
[i
* numAttr
+ 3] = resolveInfo
.activityInfo
.name
;
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
);
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
, "., ");
427 String subType
= null;
428 while (st
.hasMoreElements()) {
429 String ext
= st
.nextToken();
430 String mt
= mtm
.getMimeTypeFromExtension(ext
);
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
: "*";
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
);
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
);
466 GeckoApp
.surfaceView
.getContext().startActivity(intent
);
468 } catch(ActivityNotFoundException e
) {
473 static String
getClipboardText() {
474 Context context
= GeckoApp
.surfaceView
.getContext();
475 ClipboardManager cm
= (ClipboardManager
)
476 context
.getSystemService(Context
.CLIPBOARD_SERVICE
);
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
);
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);
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
);
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
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;