1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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
;
41 import java
.lang
.reflect
.*;
43 import java
.nio
.channels
.*;
46 import java
.util
.zip
.*;
50 import android
.text
.*;
51 import android
.view
.*;
52 import android
.view
.inputmethod
.*;
53 import android
.content
.*;
54 import android
.content
.res
.*;
55 import android
.content
.pm
.*;
56 import android
.graphics
.*;
57 import android
.widget
.*;
58 import android
.hardware
.*;
59 import android
.location
.*;
61 import android
.util
.*;
62 import android
.net
.Uri
;
63 import android
.net
.ConnectivityManager
;
64 import android
.net
.NetworkInfo
;
68 // static members only
69 private GeckoAppShell() { }
71 static private LinkedList
<GeckoEvent
> gPendingEvents
=
72 new LinkedList
<GeckoEvent
>();
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_CANCELCOMPOSITION
= 2;
83 static private final int NOTIFY_IME_FOCUSCHANGE
= 3;
85 static public final long kFreeSpaceThreshold
= 157286400L; // 150MB
86 static private final long kLibFreeSpaceBuffer
= 20971520L; // 29MB
87 static private File sCacheFile
= null;
88 static private int sFreeSpace
= -1;
90 /* The Android-side API: API methods that Android calls */
92 // Initialization methods
93 public static native void nativeInit();
94 public static native void nativeRun(String args
);
97 public static native void setSurfaceView(GeckoSurfaceView sv
);
98 public static native void putenv(String map
);
99 public static native void onResume();
100 public static native void onLowMemory();
101 public static native void callObserver(String observerKey
, String topic
, String data
);
102 public static native void removeObserver(String observerKey
);
103 public static native void loadLibs(String apkName
, boolean shouldExtract
);
104 public static native void onChangeNetworkLinkStatus(String status
);
106 public static File
getCacheDir() {
107 if (sCacheFile
== null)
108 sCacheFile
= GeckoApp
.mAppContext
.getCacheDir();
112 public static long getFreeSpace() {
114 if (sFreeSpace
== -1) {
115 File cacheDir
= getCacheDir();
116 if (cacheDir
!= null) {
117 StatFs cacheStats
= new StatFs(cacheDir
.getPath());
118 sFreeSpace
= cacheStats
.getFreeBlocks() *
119 cacheStats
.getBlockSize();
121 Log
.i("GeckoAppShell", "Unable to get cache dir");
124 } catch (Exception e
) {
125 Log
.e("GeckoAppShell", "exception while stating cache dir: ", e
);
130 static boolean moveFile(File inFile
, File outFile
)
132 Log
.i("GeckoAppShell", "moving " + inFile
+ " to " + outFile
);
133 if (outFile
.isDirectory())
134 outFile
= new File(outFile
, inFile
.getName());
136 if (inFile
.renameTo(outFile
))
138 } catch (SecurityException se
) {
139 Log
.w("GeckoAppShell", "error trying to rename file", se
);
142 long lastModified
= inFile
.lastModified();
143 outFile
.createNewFile();
144 // so copy it instead
145 FileChannel inChannel
= new FileInputStream(inFile
).getChannel();
146 FileChannel outChannel
= new FileOutputStream(outFile
).getChannel();
147 long size
= inChannel
.size();
148 long transferred
= inChannel
.transferTo(0, size
, outChannel
);
151 outFile
.setLastModified(lastModified
);
153 if (transferred
== size
)
157 } catch (Exception e
) {
158 Log
.e("GeckoAppShell", "exception while moving file: ", e
);
161 } catch (SecurityException se
) {
162 Log
.w("GeckoAppShell", "error trying to delete file", se
);
169 static boolean moveDir(File from
, File to
) {
172 if (from
.renameTo(to
))
174 } catch (SecurityException se
) {
175 Log
.w("GeckoAppShell", "error trying to rename file", se
);
177 File
[] files
= from
.listFiles();
178 boolean retVal
= true;
182 Iterator fileIterator
= Arrays
.asList(files
).iterator();
183 while (fileIterator
.hasNext()) {
184 File file
= (File
)fileIterator
.next();
185 File dest
= new File(to
, file
.getName());
186 if (file
.isDirectory())
187 retVal
= moveDir(file
, dest
) ? retVal
: false;
189 retVal
= moveFile(file
, dest
) ? retVal
: false;
192 } catch(Exception e
) {
193 Log
.e("GeckoAppShell", "error trying to move file", e
);
199 public static void loadGeckoLibs(String apkName
) {
200 // The package data lib directory isn't placed in ld.so's
201 // search path, so we have to manually load libraries that
202 // libxul will depend on. Not ideal.
203 System
.loadLibrary("mozutils");
204 GeckoApp geckoApp
= GeckoApp
.mAppContext
;
206 if (Build
.VERSION
.SDK_INT
< 8 ||
207 geckoApp
.getApplication().getPackageResourcePath().startsWith("/data")) {
208 File home
= geckoApp
.getFilesDir();
209 homeDir
= home
.getPath();
210 // handle the application being moved to phone from sdcard
211 File profileDir
= new File(homeDir
, "mozilla");
212 File oldHome
= new File("/data/data/" +
213 GeckoApp
.mAppContext
.getPackageName() + "/mozilla");
214 if (oldHome
.exists())
215 moveDir(oldHome
, profileDir
);
216 if (Build
.VERSION
.SDK_INT
>= 8) {
217 File extHome
= geckoApp
.getExternalFilesDir(null);
218 File extProf
= new File (extHome
, "mozilla");
219 if (extHome
!= null && extProf
!= null && extProf
.exists())
220 moveDir(extProf
, profileDir
);
223 File home
= geckoApp
.getExternalFilesDir(null);
224 homeDir
= home
.getPath();
225 // handle the application being moved to phone from sdcard
226 File profileDir
= new File(homeDir
, "mozilla");
227 File oldHome
= new File("/data/data/" +
228 GeckoApp
.mAppContext
.getPackageName() + "/mozilla");
229 if (oldHome
.exists())
230 moveDir(oldHome
, profileDir
);
232 File intHome
= geckoApp
.getFilesDir();
233 File intProf
= new File(intHome
, "mozilla");
234 if (intHome
!= null && intProf
!= null && intProf
.exists())
235 moveDir(intProf
, profileDir
);
237 GeckoAppShell
.putenv("HOME=" + homeDir
);
238 GeckoAppShell
.putenv("GRE_HOME=" + GeckoApp
.sGREDir
.getPath());
239 Intent i
= geckoApp
.getIntent();
240 String env
= i
.getStringExtra("env0");
241 Log
.i("GeckoApp", "env0: "+ env
);
242 for (int c
= 1; env
!= null; c
++) {
243 GeckoAppShell
.putenv(env
);
244 env
= i
.getStringExtra("env" + c
);
245 Log
.i("GeckoApp", "env"+ c
+": "+ env
);
248 File f
= geckoApp
.getDir("tmp", Context
.MODE_WORLD_READABLE
|
249 Context
.MODE_WORLD_WRITEABLE
);
254 GeckoAppShell
.putenv("TMPDIR=" + f
.getPath());
256 f
= Environment
.getDownloadCacheDirectory();
257 GeckoAppShell
.putenv("EXTERNAL_STORAGE=" + f
.getPath());
259 File cacheFile
= getCacheDir();
260 GeckoAppShell
.putenv("CACHE_PATH=" + cacheFile
.getPath());
262 // gingerbread introduces File.getUsableSpace(). We should use that.
263 long freeSpace
= getFreeSpace();
265 File downloadDir
= null;
266 File updatesDir
= null;
267 if (Build
.VERSION
.SDK_INT
>= 8) {
268 downloadDir
= Environment
.getExternalStoragePublicDirectory(Environment
.DIRECTORY_DOWNLOADS
);
269 updatesDir
= GeckoApp
.mAppContext
.getExternalFilesDir(Environment
.DIRECTORY_DOWNLOADS
);
271 updatesDir
= downloadDir
= new File(Environment
.getExternalStorageDirectory().getPath(), "download");
273 GeckoAppShell
.putenv("DOWNLOADS_DIRECTORY=" + downloadDir
.getPath());
274 GeckoAppShell
.putenv("UPDATES_DIRECTORY=" + updatesDir
.getPath());
276 catch (Exception e
) {
277 Log
.i("GeckoApp", "No download directory has been found: " + e
);
282 if (freeSpace
+ kLibFreeSpaceBuffer
< kFreeSpaceThreshold
) {
283 // remove any previously extracted libs since we're apparently low
284 File
[] files
= cacheFile
.listFiles();
286 Iterator cacheFiles
= Arrays
.asList(files
).iterator();
287 while (cacheFiles
.hasNext()) {
288 File libFile
= (File
)cacheFiles
.next();
289 if (libFile
.getName().endsWith(".so"))
294 loadLibs(apkName
, freeSpace
> kFreeSpaceThreshold
);
297 private static void putLocaleEnv() {
298 GeckoAppShell
.putenv("LANG=" + Locale
.getDefault().toString());
299 NumberFormat nf
= NumberFormat
.getInstance();
300 if (nf
instanceof DecimalFormat
) {
301 DecimalFormat df
= (DecimalFormat
)nf
;
302 DecimalFormatSymbols dfs
= df
.getDecimalFormatSymbols();
304 GeckoAppShell
.putenv("LOCALE_DECIMAL_POINT=" + dfs
.getDecimalSeparator());
305 GeckoAppShell
.putenv("LOCALE_THOUSANDS_SEP=" + dfs
.getGroupingSeparator());
306 GeckoAppShell
.putenv("LOCALE_GROUPING=" + (char)df
.getGroupingSize());
310 public static void runGecko(String apkPath
, String args
, String url
) {
311 // run gecko -- it will spawn its own thread
312 GeckoAppShell
.nativeInit();
314 // Tell Gecko where the target surface view is for rendering
315 GeckoAppShell
.setSurfaceView(GeckoApp
.surfaceView
);
317 // First argument is the .apk path
318 String combinedArgs
= apkPath
+ " -omnijar " + apkPath
;
320 combinedArgs
+= " " + args
;
322 combinedArgs
+= " " + url
;
324 GeckoAppShell
.nativeRun(combinedArgs
);
327 private static GeckoEvent mLastDrawEvent
;
329 private static void sendPendingEventsToGecko() {
331 while (!gPendingEvents
.isEmpty()) {
332 GeckoEvent e
= gPendingEvents
.removeFirst();
333 notifyGeckoOfEvent(e
);
335 } catch (NoSuchElementException e
) {}
338 public static void sendEventToGecko(GeckoEvent e
) {
339 if (GeckoApp
.checkLaunchState(GeckoApp
.LaunchState
.GeckoRunning
)) {
340 notifyGeckoOfEvent(e
);
342 gPendingEvents
.addLast(e
);
346 // Tell the Gecko event loop that an event is available.
347 public static native void notifyGeckoOfEvent(GeckoEvent event
);
350 * The Gecko-side API: API methods that Gecko calls
352 public static void scheduleRedraw() {
354 scheduleRedraw(0, -1, -1, -1, -1);
357 public static void scheduleRedraw(int nativeWindow
, int x
, int y
, int w
, int h
) {
361 e
= new GeckoEvent(GeckoEvent
.DRAW
, null);
363 e
= new GeckoEvent(GeckoEvent
.DRAW
, new Rect(x
, y
, w
, h
));
366 e
.mNativeWindow
= nativeWindow
;
371 /* Delay updating IME states (see bug 573800) */
372 private static final class IMEStateUpdater
extends TimerTask
374 static private IMEStateUpdater instance
;
375 private boolean mEnable
, mReset
;
377 static private IMEStateUpdater
getInstance() {
378 if (instance
== null) {
379 instance
= new IMEStateUpdater();
380 mIMETimer
.schedule(instance
, 200);
385 static public synchronized void enableIME() {
386 getInstance().mEnable
= true;
389 static public synchronized void resetIME() {
390 getInstance().mReset
= true;
394 synchronized(IMEStateUpdater
.class) {
398 InputMethodManager imm
= (InputMethodManager
)
399 GeckoApp
.surfaceView
.getContext().getSystemService(
400 Context
.INPUT_METHOD_SERVICE
);
405 imm
.restartInput(GeckoApp
.surfaceView
);
410 if (GeckoApp
.surfaceView
.mIMEState
!=
411 GeckoSurfaceView
.IME_STATE_DISABLED
)
412 imm
.showSoftInput(GeckoApp
.surfaceView
, 0);
414 imm
.hideSoftInputFromWindow(
415 GeckoApp
.surfaceView
.getWindowToken(), 0);
419 public static void notifyIME(int type
, int state
) {
420 if (GeckoApp
.surfaceView
== null)
424 case NOTIFY_IME_RESETINPUTSTATE
:
425 GeckoApp
.surfaceView
.inputConnection
.finishComposingText();
426 IMEStateUpdater
.resetIME();
427 // keep current enabled state
428 IMEStateUpdater
.enableIME();
431 case NOTIFY_IME_CANCELCOMPOSITION
:
432 IMEStateUpdater
.resetIME();
435 case NOTIFY_IME_FOCUSCHANGE
:
436 GeckoApp
.surfaceView
.mIMEFocus
= state
!= 0;
437 IMEStateUpdater
.resetIME();
442 public static void notifyIMEEnabled(int state
, String typeHint
,
444 if (GeckoApp
.surfaceView
== null)
447 /* When IME is 'disabled', IME processing is disabled.
448 In addition, the IME UI is hidden */
449 GeckoApp
.surfaceView
.mIMEState
= state
;
450 GeckoApp
.surfaceView
.mIMETypeHint
= typeHint
;
451 GeckoApp
.surfaceView
.mIMEActionHint
= actionHint
;
452 IMEStateUpdater
.enableIME();
455 public static void notifyIMEChange(String text
, int start
, int end
, int newEnd
) {
456 if (GeckoApp
.surfaceView
== null ||
457 GeckoApp
.surfaceView
.inputConnection
== null)
460 InputMethodManager imm
= (InputMethodManager
)
461 GeckoApp
.surfaceView
.getContext().getSystemService(
462 Context
.INPUT_METHOD_SERVICE
);
467 GeckoApp
.surfaceView
.inputConnection
.notifySelectionChange(
470 GeckoApp
.surfaceView
.inputConnection
.notifyTextChange(
471 imm
, text
, start
, end
, newEnd
);
474 public static void enableAccelerometer(boolean enable
) {
475 SensorManager sm
= (SensorManager
)
476 GeckoApp
.surfaceView
.getContext().getSystemService(Context
.SENSOR_SERVICE
);
479 Sensor accelSensor
= sm
.getDefaultSensor(Sensor
.TYPE_ACCELEROMETER
);
480 if (accelSensor
== null)
483 sm
.registerListener(GeckoApp
.surfaceView
, accelSensor
, SensorManager
.SENSOR_DELAY_GAME
);
485 sm
.unregisterListener(GeckoApp
.surfaceView
);
489 public static void enableLocation(boolean enable
) {
490 LocationManager lm
= (LocationManager
)
491 GeckoApp
.surfaceView
.getContext().getSystemService(Context
.LOCATION_SERVICE
);
494 Criteria crit
= new Criteria();
495 crit
.setAccuracy(Criteria
.ACCURACY_FINE
);
496 String provider
= lm
.getBestProvider(crit
, true);
497 if (provider
== null)
500 Location loc
= lm
.getLastKnownLocation(provider
);
502 sendEventToGecko(new GeckoEvent(loc
));
503 lm
.requestLocationUpdates(provider
, 100, (float).5, GeckoApp
.surfaceView
, Looper
.getMainLooper());
505 lm
.removeUpdates(GeckoApp
.surfaceView
);
509 public static void moveTaskToBack() {
510 GeckoApp
.mAppContext
.moveTaskToBack(true);
513 public static void returnIMEQueryResult(String result
, int selectionStart
, int selectionLength
) {
514 GeckoApp
.surfaceView
.inputConnection
.mSelectionStart
= selectionStart
;
515 GeckoApp
.surfaceView
.inputConnection
.mSelectionLength
= selectionLength
;
517 GeckoApp
.surfaceView
.inputConnection
.mQueryResult
.put(result
);
518 } catch (InterruptedException e
) {
522 static void onAppShellReady()
524 // mLaunchState can only be Launched at this point
525 GeckoApp
.setLaunchState(GeckoApp
.LaunchState
.GeckoRunning
);
526 sendPendingEventsToGecko();
529 static void onXreExit() {
530 // mLaunchState can only be Launched or GeckoRunning at this point
531 GeckoApp
.setLaunchState(GeckoApp
.LaunchState
.GeckoExiting
);
532 Log
.i("GeckoAppJava", "XRE exited");
533 if (gRestartScheduled
) {
534 GeckoApp
.mAppContext
.doRestart();
536 Log
.i("GeckoAppJava", "we're done, good bye");
537 GeckoApp
.mAppContext
.finish();
542 static void scheduleRestart() {
543 Log
.i("GeckoAppJava", "scheduling restart");
544 gRestartScheduled
= true;
547 // "Installs" an application by creating a shortcut
548 static void installWebApplication(String aURI
, String aTitle
, String aIconData
) {
549 Log
.w("GeckoAppJava", "installWebApplication for " + aURI
+ " [" + aTitle
+ "]");
551 // the intent to be launched by the shortcut
552 Intent shortcutIntent
= new Intent("org.mozilla.fennec.WEBAPP");
553 shortcutIntent
.setClassName(GeckoApp
.mAppContext
,
554 GeckoApp
.mAppContext
.getPackageName() + ".App");
555 shortcutIntent
.putExtra("args", "--webapp=" + aURI
);
557 Intent intent
= new Intent();
558 intent
.putExtra(Intent
.EXTRA_SHORTCUT_INTENT
, shortcutIntent
);
559 intent
.putExtra(Intent
.EXTRA_SHORTCUT_NAME
, aTitle
);
560 byte[] raw
= Base64
.decode(aIconData
.substring(22), Base64
.DEFAULT
);
561 Bitmap bitmap
= BitmapFactory
.decodeByteArray(raw
, 0, raw
.length
);
562 intent
.putExtra(Intent
.EXTRA_SHORTCUT_ICON
, bitmap
);
563 intent
.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
564 GeckoApp
.mAppContext
.sendBroadcast(intent
);
567 static String
[] getHandlersForMimeType(String aMimeType
, String aAction
) {
568 Intent intent
= getIntentForActionString(aAction
);
569 if (aMimeType
!= null && aMimeType
.length() > 0)
570 intent
.setType(aMimeType
);
571 return getHandlersForIntent(intent
);
574 static String
[] getHandlersForURL(String aURL
, String aAction
) {
575 // aURL may contain the whole URL or just the protocol
576 Uri uri
= aURL
.indexOf(':') >= 0 ? Uri
.parse(aURL
) : new Uri
.Builder().scheme(aURL
).build();
577 Intent intent
= getIntentForActionString(aAction
);
579 return getHandlersForIntent(intent
);
582 static String
[] getHandlersForIntent(Intent intent
) {
584 GeckoApp
.surfaceView
.getContext().getPackageManager();
585 List
<ResolveInfo
> list
= pm
.queryIntentActivities(intent
, 0);
587 String
[] ret
= new String
[list
.size() * numAttr
];
588 for (int i
= 0; i
< list
.size(); i
++) {
589 ResolveInfo resolveInfo
= list
.get(i
);
590 ret
[i
* numAttr
] = resolveInfo
.loadLabel(pm
).toString();
591 if (resolveInfo
.isDefault
)
592 ret
[i
* numAttr
+ 1] = "default";
594 ret
[i
* numAttr
+ 1] = "";
595 ret
[i
* numAttr
+ 2] = resolveInfo
.activityInfo
.applicationInfo
.packageName
;
596 ret
[i
* numAttr
+ 3] = resolveInfo
.activityInfo
.name
;
601 static Intent
getIntentForActionString(String aAction
) {
602 // Default to the view action if no other action as been specified.
603 if (aAction
!= null && aAction
.length() > 0)
604 return new Intent(aAction
);
606 return new Intent(Intent
.ACTION_VIEW
);
609 static String
getMimeTypeFromExtensions(String aFileExt
) {
610 android
.webkit
.MimeTypeMap mtm
=
611 android
.webkit
.MimeTypeMap
.getSingleton();
612 StringTokenizer st
= new StringTokenizer(aFileExt
, "., ");
614 String subType
= null;
615 while (st
.hasMoreElements()) {
616 String ext
= st
.nextToken();
617 String mt
= mtm
.getMimeTypeFromExtension(ext
);
620 int slash
= mt
.indexOf('/');
621 String tmpType
= mt
.substring(0, slash
);
622 if (!tmpType
.equalsIgnoreCase(type
))
623 type
= type
== null ? tmpType
: "*";
624 String tmpSubType
= mt
.substring(slash
+ 1);
625 if (!tmpSubType
.equalsIgnoreCase(subType
))
626 subType
= subType
== null ? tmpSubType
: "*";
632 return type
+ "/" + subType
;
635 static boolean openUriExternal(String aUriSpec
, String aMimeType
, String aPackageName
,
636 String aClassName
, String aAction
, String aTitle
) {
637 Intent intent
= getIntentForActionString(aAction
);
638 if (aAction
.equalsIgnoreCase(Intent
.ACTION_SEND
)) {
639 intent
.putExtra(Intent
.EXTRA_TEXT
, aUriSpec
);
640 intent
.putExtra(Intent
.EXTRA_SUBJECT
, aTitle
);
641 if (aMimeType
!= null && aMimeType
.length() > 0)
642 intent
.setType(aMimeType
);
643 } else if (aMimeType
.length() > 0) {
644 intent
.setDataAndType(Uri
.parse(aUriSpec
), aMimeType
);
646 intent
.setData(Uri
.parse(aUriSpec
));
648 if (aPackageName
.length() > 0 && aClassName
.length() > 0)
649 intent
.setClassName(aPackageName
, aClassName
);
651 intent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
);
653 GeckoApp
.surfaceView
.getContext().startActivity(intent
);
655 } catch(ActivityNotFoundException e
) {
660 static String
getClipboardText() {
661 Context context
= GeckoApp
.surfaceView
.getContext();
662 ClipboardManager cm
= (ClipboardManager
)
663 context
.getSystemService(Context
.CLIPBOARD_SERVICE
);
666 return cm
.getText().toString();
669 static void setClipboardText(String text
) {
670 Context context
= GeckoApp
.surfaceView
.getContext();
671 ClipboardManager cm
= (ClipboardManager
)
672 context
.getSystemService(Context
.CLIPBOARD_SERVICE
);
676 public static void showAlertNotification(String aImageUrl
, String aAlertTitle
, String aAlertText
,
677 String aAlertCookie
, String aAlertName
) {
678 Log
.i("GeckoAppJava", "GeckoAppShell.showAlertNotification\n" +
679 "- image = '" + aImageUrl
+ "'\n" +
680 "- title = '" + aAlertTitle
+ "'\n" +
681 "- text = '" + aAlertText
+"'\n" +
682 "- cookie = '" + aAlertCookie
+"'\n" +
683 "- name = '" + aAlertName
+ "'");
685 int icon
= R
.drawable
.icon
; // Just use the app icon by default
687 Uri imageUri
= Uri
.parse(aImageUrl
);
688 String scheme
= imageUri
.getScheme();
689 if ("drawable".equals(scheme
)) {
690 String resource
= imageUri
.getSchemeSpecificPart();
691 resource
= resource
.substring(resource
.lastIndexOf('/') + 1);
693 Class drawableClass
= R
.drawable
.class;
694 Field f
= drawableClass
.getField(resource
);
695 icon
= f
.getInt(null);
696 } catch (Exception e
) {} // just means the resource doesn't exist
699 int notificationID
= aAlertName
.hashCode();
701 // Remove the old notification with the same ID, if any
702 removeNotification(notificationID
);
704 AlertNotification notification
= new AlertNotification(GeckoApp
.mAppContext
,
705 notificationID
, icon
, aAlertTitle
, aAlertText
, System
.currentTimeMillis());
707 // The intent to launch when the user clicks the expanded notification
708 Intent notificationIntent
= new Intent(GeckoApp
.ACTION_ALERT_CLICK
);
709 notificationIntent
.setClassName(GeckoApp
.mAppContext
,
710 GeckoApp
.mAppContext
.getPackageName() + ".NotificationHandler");
712 // Put the strings into the intent as an URI "alert:<name>#<cookie>"
713 Uri dataUri
= Uri
.fromParts("alert", aAlertName
, aAlertCookie
);
714 notificationIntent
.setData(dataUri
);
716 PendingIntent contentIntent
= PendingIntent
.getBroadcast(GeckoApp
.mAppContext
, 0, notificationIntent
, 0);
717 notification
.setLatestEventInfo(GeckoApp
.mAppContext
, aAlertTitle
, aAlertText
, contentIntent
);
719 // The intent to execute when the status entry is deleted by the user with the "Clear All Notifications" button
720 Intent clearNotificationIntent
= new Intent(GeckoApp
.ACTION_ALERT_CLEAR
);
721 clearNotificationIntent
.setClassName(GeckoApp
.mAppContext
,
722 GeckoApp
.mAppContext
.getPackageName() + ".NotificationHandler");
723 clearNotificationIntent
.setData(dataUri
);
724 notification
.deleteIntent
= PendingIntent
.getBroadcast(GeckoApp
.mAppContext
, 0, clearNotificationIntent
, 0);
726 mAlertNotifications
.put(notificationID
, notification
);
730 Log
.i("GeckoAppJava", "Created notification ID " + notificationID
);
733 public static void alertsProgressListener_OnProgress(String aAlertName
, long aProgress
, long aProgressMax
, String aAlertText
) {
734 Log
.i("GeckoAppJava", "GeckoAppShell.alertsProgressListener_OnProgress\n" +
735 "- name = '" + aAlertName
+"', " +
736 "progress = " + aProgress
+" / " + aProgressMax
+ ", text = '" + aAlertText
+ "'");
738 int notificationID
= aAlertName
.hashCode();
739 AlertNotification notification
= mAlertNotifications
.get(notificationID
);
740 if (notification
!= null)
741 notification
.updateProgress(aAlertText
, aProgress
, aProgressMax
);
743 if (aProgress
== aProgressMax
) {
744 // Hide the notification at 100%
745 removeObserver(aAlertName
);
746 removeNotification(notificationID
);
750 public static void alertsProgressListener_OnCancel(String aAlertName
) {
751 Log
.i("GeckoAppJava", "GeckoAppShell.alertsProgressListener_OnCancel('" + aAlertName
+ "'");
753 removeObserver(aAlertName
);
755 int notificationID
= aAlertName
.hashCode();
756 removeNotification(notificationID
);
759 public static void handleNotification(String aAction
, String aAlertName
, String aAlertCookie
) {
760 int notificationID
= aAlertName
.hashCode();
762 if (GeckoApp
.ACTION_ALERT_CLICK
.equals(aAction
)) {
763 Log
.i("GeckoAppJava", "GeckoAppShell.handleNotification: callObserver(alertclickcallback)");
764 callObserver(aAlertName
, "alertclickcallback", aAlertCookie
);
766 AlertNotification notification
= mAlertNotifications
.get(notificationID
);
767 if (notification
!= null && notification
.isProgressStyle()) {
768 // When clicked, keep the notification, if it displays a progress
773 callObserver(aAlertName
, "alertfinished", aAlertCookie
);
775 removeObserver(aAlertName
);
777 removeNotification(notificationID
);
780 private static void removeNotification(int notificationID
) {
781 mAlertNotifications
.remove(notificationID
);
783 NotificationManager notificationManager
= (NotificationManager
)
784 GeckoApp
.mAppContext
.getSystemService(Context
.NOTIFICATION_SERVICE
);
785 notificationManager
.cancel(notificationID
);
788 public static int getDpi() {
789 DisplayMetrics metrics
= new DisplayMetrics();
790 GeckoApp
.mAppContext
.getWindowManager().getDefaultDisplay().getMetrics(metrics
);
791 return metrics
.densityDpi
;
794 public static void setFullScreen(boolean fullscreen
) {
795 GeckoApp
.mFullscreen
= fullscreen
;
797 // force a reconfiguration to hide/show the system bar
798 GeckoApp
.mAppContext
.setRequestedOrientation(ActivityInfo
.SCREEN_ORIENTATION_PORTRAIT
);
799 GeckoApp
.mAppContext
.setRequestedOrientation(ActivityInfo
.SCREEN_ORIENTATION_LANDSCAPE
);
800 GeckoApp
.mAppContext
.setRequestedOrientation(ActivityInfo
.SCREEN_ORIENTATION_USER
);
803 public static String
showFilePicker(String aFilters
) {
804 return GeckoApp
.mAppContext
.
805 showFilePicker(getMimeTypeFromExtensions(aFilters
));
808 public static void performHapticFeedback(boolean aIsLongPress
) {
809 GeckoApp
.surfaceView
.
810 performHapticFeedback(aIsLongPress ?
811 HapticFeedbackConstants
.LONG_PRESS
:
812 HapticFeedbackConstants
.VIRTUAL_KEY
);
815 public static void showInputMethodPicker() {
816 InputMethodManager imm
= (InputMethodManager
) GeckoApp
.surfaceView
.getContext().getSystemService(Context
.INPUT_METHOD_SERVICE
);
817 imm
.showInputMethodPicker();
820 public static void hideProgressDialog() {
821 GeckoApp
.surfaceView
.mShowingSplashScreen
= false;
824 public static void setKeepScreenOn(final boolean on
) {
825 GeckoApp
.mAppContext
.runOnUiThread(new Runnable() {
827 GeckoApp
.surfaceView
.setKeepScreenOn(on
);
832 public static boolean isNetworkLinkUp() {
833 ConnectivityManager cm
= (ConnectivityManager
)
834 GeckoApp
.mAppContext
.getSystemService(Context
.CONNECTIVITY_SERVICE
);
835 NetworkInfo info
= cm
.getActiveNetworkInfo();
836 if (info
== null || !info
.isConnected())
841 public static boolean isNetworkLinkKnown() {
842 ConnectivityManager cm
= (ConnectivityManager
)
843 GeckoApp
.mAppContext
.getSystemService(Context
.CONNECTIVITY_SERVICE
);
844 if (cm
.getActiveNetworkInfo() == null)