1 /* -*- Mode: c++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
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.org code.
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2009-2010
20 * the Initial Developer. All Rights Reserved.
23 * Vladimir Vukicevic <vladimir@pobox.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsAppShell.h"
41 #include "nsThreadUtils.h"
42 #include "nsICommandLineRunner.h"
43 #include "nsIObserverService.h"
44 #include "nsIAppStartup.h"
45 #include "nsIGeolocationProvider.h"
46 #include "nsIPrefService.h"
48 #include "mozilla/Services.h"
49 #include "mozilla/unused.h"
52 #include "AndroidBridge.h"
53 #include "nsAccelerometerSystem.h"
54 #include <android/log.h>
62 #ifdef ANDROID_DEBUG_EVENTS
63 #define EVLOG(args...) ALOG(args)
65 #define EVLOG(args...) do { } while (0)
68 using namespace mozilla
;
71 PRLogModuleInfo
*gWidgetLog
= nsnull
;
74 nsAccelerometerSystem
*gAccel
= nsnull
;
75 nsIGeolocationUpdate
*gLocationCallback
= nsnull
;
77 nsAppShell
*nsAppShell::gAppShell
= nsnull
;
79 NS_IMPL_ISUPPORTS_INHERITED1(nsAppShell
, nsBaseAppShell
, nsIObserver
)
81 nsAppShell::nsAppShell()
90 nsAppShell::~nsAppShell()
96 nsAppShell::NotifyNativeEvent()
99 PR_NotifyCondVar(mQueueCond
);
100 PR_Unlock(mCondLock
);
108 gWidgetLog
= PR_NewLogModule("Widget");
111 mQueueLock
= PR_NewLock();
112 mCondLock
= PR_NewLock();
113 mQueueCond
= PR_NewCondVar(mCondLock
);
115 mObserversHash
.Init();
117 nsresult rv
= nsBaseAppShell::Init();
118 if (AndroidBridge::Bridge())
119 AndroidBridge::Bridge()->NotifyAppShellReady();
121 nsCOMPtr
<nsIObserverService
> obsServ
=
122 mozilla::services::GetObserverService();
124 obsServ
->AddObserver(this, "xpcom-shutdown", PR_FALSE
);
130 nsAppShell::Observe(nsISupports
* aSubject
,
132 const PRUnichar
* aData
)
134 if (!strcmp(aTopic
, "xpcom-shutdown")) {
135 // We need to ensure no observers stick around after XPCOM shuts down
136 // or we'll see crashes, as the app shell outlives XPConnect.
137 mObserversHash
.Clear();
139 return nsBaseAppShell::Observe(aSubject
, aTopic
, aData
);
143 nsAppShell::ScheduleNativeEventCallback()
145 EVLOG("nsAppShell::ScheduleNativeEventCallback pth: %p thread: %p main: %d", (void*) pthread_self(), (void*) NS_GetCurrentThread(), NS_IsMainThread());
147 // this is valid to be called from any thread, so do so.
148 PostEvent(new AndroidGeckoEvent(AndroidGeckoEvent::NATIVE_POKE
));
152 nsAppShell::ProcessNextNativeEvent(PRBool mayWait
)
154 EVLOG("nsAppShell::ProcessNextNativeEvent %d", mayWait
);
158 nsAutoPtr
<AndroidGeckoEvent
> curEvent
;
159 AndroidGeckoEvent
*nextEvent
;
161 curEvent
= GetNextEvent();
162 if (!curEvent
&& mayWait
) {
163 // hmm, should we really hardcode this 10s?
164 #if defined(ANDROID_DEBUG_EVENTS)
166 EVLOG("nsAppShell: waiting on mQueueCond");
169 PR_WaitCondVar(mQueueCond
, PR_MillisecondsToInterval(10000));
171 EVLOG("nsAppShell: wait done, waited %d ms", (int)(t1
-t0
)/1000);
173 PR_WaitCondVar(mQueueCond
, PR_INTERVAL_NO_TIMEOUT
);
176 curEvent
= GetNextEvent();
179 PR_Unlock(mCondLock
);
184 // Combine subsequent events of the same type
186 nextEvent
= PeekNextEvent();
189 int curType
= curEvent
->Type();
190 int nextType
= nextEvent
->Type();
192 while (nextType
== AndroidGeckoEvent::DRAW
&&
195 // skip this draw, since there's a later one already in the queue.. this will let us
196 // deal with sequences that look like:
197 // MOVE DRAW MOVE DRAW MOVE DRAW
198 // and end up with just
200 // when we process all the events.
204 #if defined(ANDROID_DEBUG_EVENTS)
205 ALOG("# Removing DRAW event (%d outstanding)", mNumDraws
);
208 nextEvent
= PeekNextEvent();
209 nextType
= nextEvent
->Type();
212 // If the next type of event isn't the same as the current type,
213 // we don't coalesce.
214 if (nextType
!= curType
)
217 // Can only coalesce motion move events, for motion events
218 if (curType
!= AndroidGeckoEvent::MOTION_EVENT
)
221 if (!(curEvent
->Action() == AndroidMotionEvent::ACTION_MOVE
&&
222 nextEvent
->Action() == AndroidMotionEvent::ACTION_MOVE
))
225 #if defined(ANDROID_DEBUG_EVENTS)
226 ALOG("# Removing % 2d event", curType
);
230 curEvent
= nextEvent
;
231 nextEvent
= PeekNextEvent();
234 EVLOG("nsAppShell: event %p %d [ndraws %d]", (void*)curEvent
.get(), curEvent
->Type(), mNumDraws
);
236 switch (curEvent
->Type()) {
237 case AndroidGeckoEvent::NATIVE_POKE
:
238 NativeEventCallback();
241 case AndroidGeckoEvent::SENSOR_EVENT
:
242 gAccel
->AccelerationChanged(-curEvent
->X(), curEvent
->Y(), curEvent
->Z());
245 case AndroidGeckoEvent::LOCATION_EVENT
:
246 if (!gLocationCallback
)
249 if (curEvent
->GeoPosition())
250 gLocationCallback
->Update(curEvent
->GeoPosition());
252 NS_WARNING("Received location event without geoposition!");
255 case AndroidGeckoEvent::ACTIVITY_STOPPING
: {
256 nsCOMPtr
<nsIObserverService
> obsServ
=
257 mozilla::services::GetObserverService();
258 NS_NAMED_LITERAL_STRING(minimize
, "heap-minimize");
259 obsServ
->NotifyObservers(nsnull
, "memory-pressure", minimize
.get());
264 case AndroidGeckoEvent::ACTIVITY_SHUTDOWN
: {
265 nsCOMPtr
<nsIObserverService
> obsServ
=
266 mozilla::services::GetObserverService();
267 NS_NAMED_LITERAL_STRING(context
, "shutdown-persist");
268 obsServ
->NotifyObservers(nsnull
, "quit-application-granted", nsnull
);
269 obsServ
->NotifyObservers(nsnull
, "quit-application-forced", nsnull
);
270 obsServ
->NotifyObservers(nsnull
, "profile-change-net-teardown", context
.get());
271 obsServ
->NotifyObservers(nsnull
, "profile-change-teardown", context
.get());
272 obsServ
->NotifyObservers(nsnull
, "profile-before-change", context
.get());
273 nsCOMPtr
<nsIAppStartup
> appSvc
= do_GetService("@mozilla.org/toolkit/app-startup;1");
275 appSvc
->Quit(nsIAppStartup::eForceQuit
);
279 case AndroidGeckoEvent::ACTIVITY_PAUSING
: {
280 // We really want to send a notification like profile-before-change,
281 // but profile-before-change ends up shutting some things down instead
283 nsCOMPtr
<nsIPrefService
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
285 prefs
->SavePrefFile(nsnull
);
290 case AndroidGeckoEvent::LOAD_URI
: {
291 nsCOMPtr
<nsICommandLineRunner
> cmdline
292 (do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
296 char *uri
= ToNewUTF8String(curEvent
->Characters());
300 const char *argv
[3] = {
305 nsresult rv
= cmdline
->Init(3, const_cast<char **>(argv
), nsnull
, nsICommandLine::STATE_REMOTE_AUTO
);
306 if (NS_SUCCEEDED(rv
))
313 nsWindow::OnGlobalAndroidEvent(curEvent
);
316 EVLOG("nsAppShell: -- done event %p %d", (void*)curEvent
.get(), curEvent
->Type());
322 nsAppShell::GetNextEvent()
324 AndroidGeckoEvent
*ae
= nsnull
;
326 if (mEventQueue
.Length()) {
328 mEventQueue
.RemoveElementAt(0);
329 if (ae
->Type() == AndroidGeckoEvent::DRAW
) {
333 PR_Unlock(mQueueLock
);
339 nsAppShell::PeekNextEvent()
341 AndroidGeckoEvent
*ae
= nsnull
;
343 if (mEventQueue
.Length()) {
346 PR_Unlock(mQueueLock
);
352 nsAppShell::PostEvent(AndroidGeckoEvent
*ae
)
355 mEventQueue
.AppendElement(ae
);
356 if (ae
->Type() == AndroidGeckoEvent::DRAW
) {
359 PR_Unlock(mQueueLock
);
364 nsAppShell::RemoveNextEvent()
366 AndroidGeckoEvent
*ae
= nsnull
;
368 if (mEventQueue
.Length()) {
370 mEventQueue
.RemoveElementAt(0);
371 if (ae
->Type() == AndroidGeckoEvent::DRAW
) {
375 PR_Unlock(mQueueLock
);
379 nsAppShell::OnResume()
384 nsAppShell::AddObserver(const nsAString
&aObserverKey
, nsIObserver
*aObserver
)
386 NS_ASSERTION(aObserver
!= nsnull
, "nsAppShell::AddObserver: aObserver is null!");
387 return mObserversHash
.Put(aObserverKey
, aObserver
) ? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
391 * The XPCOM event that will call the observer on the main thread.
393 class ObserverCaller
: public nsRunnable
{
395 ObserverCaller(nsIObserver
*aObserver
, const char *aTopic
, const PRUnichar
*aData
) :
396 mObserver(aObserver
), mTopic(aTopic
), mData(aData
) {
397 NS_ASSERTION(aObserver
!= nsnull
, "ObserverCaller: aObserver is null!");
401 ALOG("ObserverCaller::Run: observer = %p, topic = '%s')",
402 (nsIObserver
*)mObserver
, mTopic
.get());
403 mObserver
->Observe(nsnull
, mTopic
.get(), mData
.get());
408 nsCOMPtr
<nsIObserver
> mObserver
;
414 nsAppShell::CallObserver(const nsAString
&aObserverKey
, const nsAString
&aTopic
, const nsAString
&aData
)
416 nsCOMPtr
<nsIObserver
> observer
;
417 mObserversHash
.Get(aObserverKey
, getter_AddRefs(observer
));
420 ALOG("nsAppShell::CallObserver: Observer was not found!");
424 const NS_ConvertUTF16toUTF8
sTopic(aTopic
);
425 const nsPromiseFlatString
& sData
= PromiseFlatString(aData
);
427 if (NS_IsMainThread()) {
428 // This branch will unlikely be hit, have it just in case
429 observer
->Observe(nsnull
, sTopic
.get(), sData
.get());
431 // Java is not running on main thread, so we have to use NS_DispatchToMainThread
432 nsCOMPtr
<nsIRunnable
> observerCaller
= new ObserverCaller(observer
, sTopic
.get(), sData
.get());
433 nsresult rv
= NS_DispatchToMainThread(observerCaller
);
434 ALOG("NS_DispatchToMainThread result: %d", rv
);
440 nsAppShell::RemoveObserver(const nsAString
&aObserverKey
)
442 mObserversHash
.Remove(aObserverKey
);
445 // NotifyObservers support. NotifyObservers only works on main thread.
447 class NotifyObserversCaller
: public nsRunnable
{
449 NotifyObserversCaller(nsISupports
*aSupports
,
450 const char *aTopic
, const PRUnichar
*aData
) :
451 mSupports(aSupports
), mTopic(aTopic
), mData(aData
) {
455 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
457 os
->NotifyObservers(mSupports
, mTopic
.get(), mData
.get());
463 nsCOMPtr
<nsISupports
> mSupports
;
469 nsAppShell::NotifyObservers(nsISupports
*aSupports
,
471 const PRUnichar
*aData
)
473 // This isn't main thread, so post this to main thread
474 nsCOMPtr
<nsIRunnable
> caller
=
475 new NotifyObserversCaller(aSupports
, aTopic
, aData
);
476 NS_DispatchToMainThread(caller
);
482 bool ProcessNextEvent()
484 return nsAppShell::gAppShell
->ProcessNextNativeEvent(PR_TRUE
) ? true : false;
489 nsAppShell::gAppShell
->NotifyNativeEvent();