Merge pull request #2059 from mcrosson/android-feature-external_player
[vuplus_xbmc] / xbmc / android / activity / XBMCApp.cpp
1 /*
2  *      Copyright (C) 2012 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include <sstream>
22
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <dlfcn.h>
26 #include <string.h>
27
28 #include <android/native_window.h>
29 #include <android/configuration.h>
30 #include <jni.h>
31
32 #include "XBMCApp.h"
33
34 #include "input/MouseStat.h"
35 #include "input/XBMC_keysym.h"
36 #include "guilib/Key.h"
37 #include "windowing/XBMC_events.h"
38 #include <android/log.h>
39
40 #include "Application.h"
41 #include "settings/AdvancedSettings.h"
42 #include "xbmc.h"
43 #include "windowing/WinEvents.h"
44 #include "guilib/GUIWindowManager.h"
45 #include "utils/log.h"
46 #include "ApplicationMessenger.h"
47
48 #define GIGABYTES       1073741824
49
50 using namespace std;
51
52 template<class T, void(T::*fn)()>
53 void* thread_run(void* obj)
54 {
55   (static_cast<T*>(obj)->*fn)();
56   return NULL;
57 }
58
59 ANativeActivity *CXBMCApp::m_activity = NULL;
60 ANativeWindow* CXBMCApp::m_window = NULL;
61
62 CXBMCApp::CXBMCApp(ANativeActivity *nativeActivity)
63   : m_wakeLock(NULL)
64 {
65   m_activity = nativeActivity;
66   
67   if (m_activity == NULL)
68   {
69     android_printf("CXBMCApp: invalid ANativeActivity instance");
70     exit(1);
71     return;
72   }
73
74   m_state.appState = Uninitialized;
75
76   if (pthread_mutex_init(&m_state.mutex, NULL) != 0)
77   {
78     android_printf("CXBMCApp: pthread_mutex_init() failed");
79     m_state.appState = Error;
80     exit(1);
81     return;
82   }
83
84 }
85
86 CXBMCApp::~CXBMCApp()
87 {
88   stop();
89
90   pthread_mutex_destroy(&m_state.mutex);
91 }
92
93 ActivityResult CXBMCApp::onActivate()
94 {
95   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
96
97   switch (m_state.appState)
98   {
99     case Uninitialized:
100       acquireWakeLock();
101       
102       pthread_attr_t attr;
103       pthread_attr_init(&attr);
104       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
105       pthread_create(&m_state.thread, &attr, thread_run<CXBMCApp, &CXBMCApp::run>, this);
106       pthread_attr_destroy(&attr);
107       break;
108       
109     case Unfocused:
110       XBMC_Pause(false);
111       setAppState(Rendering);
112       break;
113       
114     case Paused:
115       acquireWakeLock();
116       
117       XBMC_SetupDisplay();
118       XBMC_Pause(false);
119       setAppState(Rendering);
120       break;
121
122     case Initialized:
123     case Rendering:
124     case Stopping:
125     case Stopped:
126     case Error:
127     default:
128       break;
129   }
130   return ActivityOK;
131 }
132
133 void CXBMCApp::onDeactivate()
134 {
135   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
136   // this is called on pause, stop and window destroy which
137   // require specific (and different) actions
138 }
139
140 void CXBMCApp::onStart()
141 {
142   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
143   // wait for onCreateWindow() and onGainFocus()
144 }
145
146 void CXBMCApp::onResume()
147 {
148   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
149   // wait for onCreateWindow() and onGainFocus()
150 }
151
152 void CXBMCApp::onPause()
153 {
154   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
155   // wait for onDestroyWindow() and/or onLostFocus()
156 }
157
158 void CXBMCApp::onStop()
159 {
160   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
161   // everything has been handled in onLostFocus() so wait
162   // if onDestroy() is called
163 }
164
165 void CXBMCApp::onDestroy()
166 {
167   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
168   stop();
169 }
170
171 void CXBMCApp::onSaveState(void **data, size_t *size)
172 {
173   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
174   // no need to save anything as XBMC is running in its own thread
175 }
176
177 void CXBMCApp::onConfigurationChanged()
178 {
179   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
180   // ignore any configuration changes like screen rotation etc
181 }
182
183 void CXBMCApp::onLowMemory()
184 {
185   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
186   // can't do much as we don't want to close completely
187 }
188
189 void CXBMCApp::onCreateWindow(ANativeWindow* window)
190 {
191   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
192   if (window == NULL)
193   {
194     android_printf(" => invalid ANativeWindow object");
195     return;
196   }
197   m_window = window;
198 }
199
200 void CXBMCApp::onResizeWindow()
201 {
202   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
203   // no need to do anything because we are fixed in fullscreen landscape mode
204 }
205
206 void CXBMCApp::onDestroyWindow()
207 {
208   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
209
210   if (m_state.appState < Paused)
211   {
212     XBMC_DestroyDisplay();
213     setAppState(Paused);
214     releaseWakeLock();
215   }
216 }
217
218 void CXBMCApp::onGainFocus()
219 {
220   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
221   // everything is handled in onActivate()
222 }
223
224 void CXBMCApp::onLostFocus()
225 {
226   android_printf("%s: %d", __PRETTY_FUNCTION__, m_state.appState);
227   switch (m_state.appState)
228   {
229     case Initialized:
230     case Rendering:
231       XBMC_Pause(true);
232       setAppState(Unfocused);
233       break;
234
235     default:
236       break;
237   }
238 }
239
240 bool CXBMCApp::getWakeLock(JNIEnv *env)
241 {
242   android_printf("%s", __PRETTY_FUNCTION__);
243   if (m_activity == NULL)
244   {
245     android_printf("  missing activity => unable to use WakeLocks");
246     return false;
247   }
248
249   if (env == NULL)
250     return false;
251
252   if (m_wakeLock == NULL)
253   {
254     jobject oActivity = m_activity->clazz;
255     jclass cActivity = env->GetObjectClass(oActivity);
256
257     // get the wake lock
258     jmethodID midActivityGetSystemService = env->GetMethodID(cActivity, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
259     jstring sPowerService = env->NewStringUTF("power"); // POWER_SERVICE
260     jobject oPowerManager = env->CallObjectMethod(oActivity, midActivityGetSystemService, sPowerService);
261
262     jclass cPowerManager = env->GetObjectClass(oPowerManager);
263     jmethodID midNewWakeLock = env->GetMethodID(cPowerManager, "newWakeLock", "(ILjava/lang/String;)Landroid/os/PowerManager$WakeLock;");
264     jstring sXbmcPackage = env->NewStringUTF("org.xbmc.xbmc");
265     jobject oWakeLock = env->CallObjectMethod(oPowerManager, midNewWakeLock, (jint)0x1a /* FULL_WAKE_LOCK */, sXbmcPackage);
266     m_wakeLock = env->NewGlobalRef(oWakeLock);
267
268     env->DeleteLocalRef(oWakeLock);
269     env->DeleteLocalRef(cPowerManager);
270     env->DeleteLocalRef(oPowerManager);
271     env->DeleteLocalRef(sPowerService);
272     env->DeleteLocalRef(sXbmcPackage);
273     env->DeleteLocalRef(cActivity);
274   }
275
276   return m_wakeLock != NULL;
277 }
278
279 void CXBMCApp::acquireWakeLock()
280 {
281   if (m_activity == NULL)
282     return;
283
284   JNIEnv *env = NULL;
285   AttachCurrentThread(&env);
286
287   if (!getWakeLock(env))
288   {
289     android_printf("%s: unable to acquire a WakeLock");
290     return;
291   }
292
293   jclass cWakeLock = env->GetObjectClass(m_wakeLock);
294   jmethodID midWakeLockAcquire = env->GetMethodID(cWakeLock, "acquire", "()V");
295   env->CallVoidMethod(m_wakeLock, midWakeLockAcquire);
296   env->DeleteLocalRef(cWakeLock);
297
298   DetachCurrentThread();
299 }
300
301 void CXBMCApp::releaseWakeLock()
302 {
303   if (m_activity == NULL)
304     return;
305
306   JNIEnv *env = NULL;
307   AttachCurrentThread(&env);
308
309   if (!getWakeLock(env))
310   {
311     android_printf("%s: unable to release a WakeLock");
312     return;
313   }
314
315   jclass cWakeLock = env->GetObjectClass(m_wakeLock);
316   jmethodID midWakeLockRelease = env->GetMethodID(cWakeLock, "release", "()V");
317   env->CallVoidMethod(m_wakeLock, midWakeLockRelease);
318   env->DeleteLocalRef(cWakeLock);
319
320   DetachCurrentThread();
321 }
322
323 void CXBMCApp::run()
324 {
325     int status = 0;
326     setAppState(Initialized);
327
328     android_printf(" => running XBMC_Run...");
329     try
330     {
331       setAppState(Rendering);
332       status = XBMC_Run(true);
333       android_printf(" => XBMC_Run finished with %d", status);
334     }
335     catch(...)
336     {
337       android_printf("ERROR: Exception caught on main loop. Exiting");
338       setAppState(Error);
339     }
340
341   bool finishActivity = false;
342   pthread_mutex_lock(&m_state.mutex);
343   finishActivity = m_state.appState != Stopping;
344   m_state.appState = Stopped;
345   pthread_mutex_unlock(&m_state.mutex);
346   
347   if (finishActivity)
348   {
349     android_printf(" => calling ANativeActivity_finish()");
350     ANativeActivity_finish(m_activity);
351   }
352 }
353
354 void CXBMCApp::stop()
355 {
356   android_printf("%s", __PRETTY_FUNCTION__);
357
358   pthread_mutex_lock(&m_state.mutex);
359   if (m_state.appState < Stopped)
360   {
361     m_state.appState = Stopping;
362     pthread_mutex_unlock(&m_state.mutex);
363     
364     android_printf(" => executing XBMC_Stop");
365     XBMC_Stop();
366     android_printf(" => waiting for XBMC to finish");
367     pthread_join(m_state.thread, NULL);
368     android_printf(" => XBMC finished");
369   }
370   else
371     pthread_mutex_unlock(&m_state.mutex);
372   
373   if (m_wakeLock != NULL && m_activity != NULL)
374   {
375     JNIEnv *env = NULL;
376     m_activity->vm->AttachCurrentThread(&env, NULL);
377     
378     env->DeleteGlobalRef(m_wakeLock);
379     m_wakeLock = NULL;
380   }
381 }
382
383 void CXBMCApp::setAppState(AppState state)
384 {
385   pthread_mutex_lock(&m_state.mutex);
386   m_state.appState = state;
387   pthread_mutex_unlock(&m_state.mutex);
388 }
389
390 void CXBMCApp::XBMC_Pause(bool pause)
391 {
392   android_printf("XBMC_Pause(%s)", pause ? "true" : "false");
393   // Only send the PAUSE action if we are pausing XBMC and something is currently playing
394   if (pause && g_application.IsPlaying() && !g_application.IsPaused())
395     CApplicationMessenger::Get().SendAction(CAction(ACTION_PAUSE), WINDOW_INVALID, true);
396
397   g_application.m_AppActive = g_application.m_AppFocused = !pause;
398 }
399
400 void CXBMCApp::XBMC_Stop()
401 {
402   CApplicationMessenger::Get().Quit();
403 }
404
405 bool CXBMCApp::XBMC_SetupDisplay()
406 {
407   android_printf("XBMC_SetupDisplay()");
408   return CApplicationMessenger::Get().SetupDisplay();
409 }
410
411 bool CXBMCApp::XBMC_DestroyDisplay()
412 {
413   android_printf("XBMC_DestroyDisplay()");
414   return CApplicationMessenger::Get().DestroyDisplay();
415 }
416
417 int CXBMCApp::AttachCurrentThread(JNIEnv** p_env, void* thr_args /* = NULL */)
418 {
419   // Until a thread is attached, it has no JNIEnv, and cannot make JNI calls.
420   // The JNIEnv is used for thread-local storage. For this reason,
421   //  you cannot share a JNIEnv between threads.
422   // If a thread is attached to JNIEnv and garbage collection is in progress,
423   //  or the debugger has issued a suspend request, Android will
424   //  pause the thread the next time it makes a JNI call.
425   return m_activity->vm->AttachCurrentThread(p_env, thr_args);
426 }
427
428 int CXBMCApp::DetachCurrentThread()
429 {
430   // Threads attached through JNIEnv must
431   // call DetachCurrentThread before they exit
432   return m_activity->vm->DetachCurrentThread();
433 }
434
435 int CXBMCApp::SetBuffersGeometry(int width, int height, int format)
436 {
437   return ANativeWindow_setBuffersGeometry(m_window, width, height, format);
438 }
439
440 int CXBMCApp::android_printf(const char *format, ...)
441 {
442   // For use before CLog is setup by XBMC_Run()
443   va_list args;
444   va_start(args, format);
445   int result = __android_log_vprint(ANDROID_LOG_VERBOSE, "XBMC", format, args);
446   va_end(args);
447   return result;
448 }
449
450 int CXBMCApp::GetDPI()
451 {
452   if (m_activity == NULL || m_activity->assetManager == NULL)
453     return 0;
454
455   // grab DPI from the current configuration - this is approximate
456   // but should be close enough for what we need
457   AConfiguration *config = AConfiguration_new();
458   AConfiguration_fromAssetManager(config, m_activity->assetManager);
459   int dpi = AConfiguration_getDensity(config);
460   AConfiguration_delete(config);
461
462   return dpi;
463 }
464
465 bool CXBMCApp::ListApplications(vector<androidPackage> *applications)
466 {
467   if (!m_activity)
468     return false;
469
470   JNIEnv *env = NULL;
471   AttachCurrentThread(&env);
472   jobject oActivity = m_activity->clazz;
473   jclass cActivity = env->GetObjectClass(oActivity);
474
475   // oPackageManager = new PackageManager();
476   jmethodID mgetPackageManager = env->GetMethodID(cActivity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
477   jobject oPackageManager = (jobject)env->CallObjectMethod(oActivity, mgetPackageManager);
478   env->DeleteLocalRef(cActivity);
479
480   // adata[] = oPackageManager.getInstalledApplications(0);
481   jclass cPackageManager = env->GetObjectClass(oPackageManager);
482   jmethodID mgetInstalledApplications = env->GetMethodID(cPackageManager, "getInstalledApplications", "(I)Ljava/util/List;");
483   jmethodID mgetApplicationLabel = env->GetMethodID(cPackageManager, "getApplicationLabel", "(Landroid/content/pm/ApplicationInfo;)Ljava/lang/CharSequence;");
484   jobject odata = env->CallObjectMethod(oPackageManager, mgetInstalledApplications, 0);
485   jclass cdata = env->GetObjectClass(odata);
486   jmethodID mtoArray = env->GetMethodID(cdata, "toArray", "()[Ljava/lang/Object;");
487   jobjectArray adata = (jobjectArray)env->CallObjectMethod(odata, mtoArray);
488   env->DeleteLocalRef(cdata);
489   env->DeleteLocalRef(odata);
490   env->DeleteLocalRef(cPackageManager);
491
492   int size = env->GetArrayLength(adata);
493   for (int i = 0; i < size; i++)
494   {
495     // oApplicationInfo = adata[i];
496     jobject oApplicationInfo = env->GetObjectArrayElement(adata, i);
497     jclass cApplicationInfo = env->GetObjectClass(oApplicationInfo);
498     jfieldID mclassName = env->GetFieldID(cApplicationInfo, "packageName", "Ljava/lang/String;");
499     jstring sapplication = (jstring)env->GetObjectField(oApplicationInfo, mclassName);
500
501     if (!sapplication)
502     {
503       env->DeleteLocalRef(cApplicationInfo);
504       env->DeleteLocalRef(oApplicationInfo);
505       continue;
506     }
507     // cname = oApplicationInfo.packageName;
508     const char* cname = env->GetStringUTFChars(sapplication, NULL);
509     androidPackage desc;
510     desc.packageName = cname;
511     env->ReleaseStringUTFChars(sapplication, cname);
512     env->DeleteLocalRef(sapplication);
513     env->DeleteLocalRef(cApplicationInfo);
514
515     jstring spackageLabel = (jstring) env->CallObjectMethod(oPackageManager, mgetApplicationLabel, oApplicationInfo);
516     if (!spackageLabel)
517     {
518       env->DeleteLocalRef(oApplicationInfo);
519       continue;
520     }
521     // cname = opackageManager.getApplicationLabel(oApplicationInfo);
522     const char* cpackageLabel = env->GetStringUTFChars(spackageLabel, NULL);
523     desc.packageLabel = cpackageLabel;
524     env->ReleaseStringUTFChars(spackageLabel, cpackageLabel);
525     env->DeleteLocalRef(spackageLabel);
526     env->DeleteLocalRef(oApplicationInfo);
527
528     if (!HasLaunchIntent(desc.packageName))
529       continue;
530
531     applications->push_back(desc);
532   }
533   env->DeleteLocalRef(oPackageManager);
534   DetachCurrentThread();
535   return true;
536 }
537
538 bool CXBMCApp::GetIconSize(const string &packageName, int *width, int *height)
539 {
540   if (!m_activity)
541     return false;
542
543   jthrowable exc;
544   JNIEnv *env = NULL;
545   AttachCurrentThread(&env);
546
547   jobject oActivity = m_activity->clazz;
548   jclass cActivity = env->GetObjectClass(oActivity);
549
550   // oPackageManager = new PackageManager();
551   jmethodID mgetPackageManager = env->GetMethodID(cActivity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
552   jobject oPackageManager = (jobject)env->CallObjectMethod(oActivity, mgetPackageManager);
553   env->DeleteLocalRef(cActivity);
554
555   jclass cPackageManager = env->GetObjectClass(oPackageManager);
556   jmethodID mgetApplicationIcon = env->GetMethodID(cPackageManager, "getApplicationIcon", "(Ljava/lang/String;)Landroid/graphics/drawable/Drawable;");
557   env->DeleteLocalRef(cPackageManager);
558
559   jclass cBitmapDrawable = env->FindClass("android/graphics/drawable/BitmapDrawable");
560   jmethodID mBitmapDrawableCtor = env->GetMethodID(cBitmapDrawable, "<init>", "()V");
561   jmethodID mgetBitmap = env->GetMethodID(cBitmapDrawable, "getBitmap", "()Landroid/graphics/Bitmap;");
562
563   // BitmapDrawable oBitmapDrawable;
564   jobject oBitmapDrawable = env->NewObject(cBitmapDrawable, mBitmapDrawableCtor);
565   jstring sPackageName = env->NewStringUTF(packageName.c_str());
566
567   // oBitmapDrawable = oPackageManager.getApplicationIcon(sPackageName)
568   oBitmapDrawable =  env->CallObjectMethod(oPackageManager, mgetApplicationIcon, sPackageName);
569   jobject oBitmap = env->CallObjectMethod(oBitmapDrawable, mgetBitmap);
570   env->DeleteLocalRef(sPackageName);
571   env->DeleteLocalRef(cBitmapDrawable);
572   env->DeleteLocalRef(oBitmapDrawable);
573   env->DeleteLocalRef(oPackageManager);
574   exc = env->ExceptionOccurred();
575   if (exc)
576   {
577     CLog::Log(LOGERROR, "CXBMCApp::GetIconSize Error getting icon size for  %s. Exception follows:", packageName.c_str());
578     env->ExceptionDescribe();
579     env->ExceptionClear();
580     env->DeleteLocalRef(oBitmap);
581     DetachCurrentThread();
582     return false;
583   } 
584   jclass cBitmap = env->GetObjectClass(oBitmap);
585   jmethodID mgetWidth = env->GetMethodID(cBitmap, "getWidth", "()I");
586   jmethodID mgetHeight = env->GetMethodID(cBitmap, "getHeight", "()I");
587   env->DeleteLocalRef(cBitmap);
588
589   // width = oBitmap.getWidth;
590   *width = (int)env->CallIntMethod(oBitmap, mgetWidth);
591
592   exc = env->ExceptionOccurred();
593   if (exc)
594   {
595     CLog::Log(LOGERROR, "CXBMCApp::GetIconSize Error getting icon width for %s. Exception follows:", packageName.c_str());
596     env->ExceptionDescribe();
597     env->ExceptionClear();
598     env->DeleteLocalRef(oBitmap);
599     DetachCurrentThread();
600     return false;
601   }
602   // height = oBitmap.getHeight;
603   *height = (int)env->CallIntMethod(oBitmap, mgetHeight);
604   env->DeleteLocalRef(oBitmap);
605
606   exc = env->ExceptionOccurred();
607   if (exc)
608   {
609     CLog::Log(LOGERROR, "CXBMCApp::GetIconSize Error getting icon height for %s. Exception follows:", packageName.c_str());
610     env->ExceptionDescribe();
611     env->ExceptionClear();
612     DetachCurrentThread();
613     return false;
614   }
615
616   DetachCurrentThread();
617   return true;
618 }
619
620 bool CXBMCApp::GetIcon(const string &packageName, void* buffer, unsigned int bufSize)
621 {
622   if (!m_activity)
623     return false;
624
625   jthrowable exc;
626   JNIEnv *env = NULL;
627   AttachCurrentThread(&env);
628
629   CLog::Log(LOGERROR, "CXBMCApp::GetIconSize Looking for: %s", packageName.c_str());
630
631   jobject oActivity = m_activity->clazz;
632   jclass cActivity = env->GetObjectClass(oActivity);
633
634   // oPackageManager = new PackageManager();
635   jmethodID mgetPackageManager = env->GetMethodID(cActivity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
636   jobject oPackageManager = (jobject)env->CallObjectMethod(oActivity, mgetPackageManager);
637   env->DeleteLocalRef(cActivity);
638
639   jclass cPackageManager = env->GetObjectClass(oPackageManager);
640   jmethodID mgetApplicationIcon = env->GetMethodID(cPackageManager, "getApplicationIcon", "(Ljava/lang/String;)Landroid/graphics/drawable/Drawable;");
641   env->DeleteLocalRef(cPackageManager);
642
643   jclass cBitmapDrawable = env->FindClass("android/graphics/drawable/BitmapDrawable");
644   jmethodID mBitmapDrawableCtor = env->GetMethodID(cBitmapDrawable, "<init>", "()V");
645   jmethodID mgetBitmap = env->GetMethodID(cBitmapDrawable, "getBitmap", "()Landroid/graphics/Bitmap;");
646
647    // BitmapDrawable oBitmapDrawable;
648   jobject oBitmapDrawable = env->NewObject(cBitmapDrawable, mBitmapDrawableCtor);
649   jstring sPackageName = env->NewStringUTF(packageName.c_str());
650
651   // oBitmapDrawable = oPackageManager.getApplicationIcon(sPackageName)
652   oBitmapDrawable =  env->CallObjectMethod(oPackageManager, mgetApplicationIcon, sPackageName);
653   env->DeleteLocalRef(sPackageName);
654   env->DeleteLocalRef(cBitmapDrawable);
655   env->DeleteLocalRef(oPackageManager);
656   exc = env->ExceptionOccurred();
657   if (exc)
658   {
659     CLog::Log(LOGERROR, "CXBMCApp::GetIcon Error getting icon for  %s. Exception follows:", packageName.c_str());
660     env->ExceptionDescribe();
661     env->ExceptionClear();
662     DetachCurrentThread();
663     return false;
664   }
665   jobject oBitmap = env->CallObjectMethod(oBitmapDrawable, mgetBitmap);
666   env->DeleteLocalRef(oBitmapDrawable);
667   jclass cBitmap = env->GetObjectClass(oBitmap);
668   jmethodID mcopyPixelsToBuffer = env->GetMethodID(cBitmap, "copyPixelsToBuffer", "(Ljava/nio/Buffer;)V");
669   jobject oPixels = env->NewDirectByteBuffer(buffer,bufSize);
670   env->DeleteLocalRef(cBitmap);
671
672   // memcpy(buffer,oPixels,bufSize); 
673   env->CallVoidMethod(oBitmap, mcopyPixelsToBuffer, oPixels);
674   env->DeleteLocalRef(oPixels);
675   env->DeleteLocalRef(oBitmap);
676   exc = env->ExceptionOccurred();
677   if (exc)
678   {
679     CLog::Log(LOGERROR, "CXBMCApp::GetIcon Error copying icon for  %s. Exception follows:", packageName.c_str());
680     env->ExceptionDescribe();
681     env->ExceptionClear();
682     DetachCurrentThread();
683     return false;
684   }
685   DetachCurrentThread();
686   return true;
687 }
688
689
690 bool CXBMCApp::HasLaunchIntent(const string &package)
691 {
692   if (!m_activity)
693     return false;
694
695   JNIEnv *env = NULL;
696   AttachCurrentThread(&env);
697
698   jthrowable exc;
699   jobject oActivity = m_activity->clazz;
700   jclass cActivity = env->GetObjectClass(oActivity);
701
702   // oPackageManager = new PackageManager();
703   jmethodID mgetPackageManager = env->GetMethodID(cActivity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
704   jobject oPackageManager = (jobject)env->CallObjectMethod(oActivity, mgetPackageManager);
705
706   // oPackageIntent = oPackageManager.getLaunchIntentForPackage(package);
707   jclass cPackageManager = env->GetObjectClass(oPackageManager);
708   jmethodID mgetLaunchIntentForPackage = env->GetMethodID(cPackageManager, "getLaunchIntentForPackage", "(Ljava/lang/String;)Landroid/content/Intent;");
709   jstring sPackageName = env->NewStringUTF(package.c_str());
710   jobject oPackageIntent = env->CallObjectMethod(oPackageManager, mgetLaunchIntentForPackage, sPackageName);
711   env->DeleteLocalRef(sPackageName);
712   env->DeleteLocalRef(cPackageManager);
713   env->DeleteLocalRef(oPackageManager);
714
715   exc = env->ExceptionOccurred();
716   if (exc)
717   {
718     CLog::Log(LOGERROR, "CXBMCApp::HasLaunchIntent Error checking for  Launch Intent for %s. Exception follows:", package.c_str());
719     env->ExceptionDescribe();
720     env->ExceptionClear();
721     return false;
722   }
723   if (!oPackageIntent)
724   {
725     return false;
726   }
727
728   env->DeleteLocalRef(oPackageIntent);
729   return true;
730 }
731
732 // Note intent, dataType, dataURI all default to ""
733 bool CXBMCApp::StartActivity(const string &package, const string &intent, const string &dataType, const string &dataURI)
734 {
735   if (!m_activity || !package.size())
736    return false;
737
738   CLog::Log(LOGDEBUG, "CXBMCApp::StartActivity package: '%s' intent: '%s' dataType: '%s' dataURI: '%s'", package.c_str(), intent.c_str(), dataType.c_str(), dataURI.c_str());
739
740   jthrowable exc;
741   JNIEnv *env = NULL;
742   AttachCurrentThread(&env);
743   
744   jobject oActivity = m_activity->clazz;
745   jclass cActivity = env->GetObjectClass(oActivity);
746
747   jobject oIntent = NULL;
748   jclass cIntent = NULL;
749   if (intent.size())
750   {
751     // Java equivalent for following JNI
752     //    Intent oIntent = new Intent(Intent.ACTION_VIEW);
753     cIntent = env->FindClass("android/content/Intent");
754     jmethodID midIntentCtor = env->GetMethodID(cIntent, "<init>", "(Ljava/lang/String;)V");
755     jstring sIntent = env->NewStringUTF(intent.c_str());
756     oIntent = env->NewObject(cIntent, midIntentCtor, sIntent);
757     env->DeleteLocalRef(sIntent);
758   }
759   else
760   {
761     // oPackageManager = new PackageManager();
762     jmethodID mgetPackageManager = env->GetMethodID(cActivity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
763     jobject oPackageManager = (jobject)env->CallObjectMethod(oActivity, mgetPackageManager);
764
765     // oPackageIntent = oPackageManager.getLaunchIntentForPackage(package);
766     jclass cPackageManager = env->GetObjectClass(oPackageManager);
767     jmethodID mgetLaunchIntentForPackage = env->GetMethodID(cPackageManager, "getLaunchIntentForPackage", "(Ljava/lang/String;)Landroid/content/Intent;");
768     jstring sPackageName = env->NewStringUTF(package.c_str());
769     oIntent = env->CallObjectMethod(oPackageManager, mgetLaunchIntentForPackage, sPackageName);
770     cIntent = env->GetObjectClass(oIntent);
771     env->DeleteLocalRef(cPackageManager);
772     env->DeleteLocalRef(sPackageName);
773     env->DeleteLocalRef(oPackageManager);
774
775     exc = env->ExceptionOccurred();
776     if (exc)
777     {
778       CLog::Log(LOGERROR, "CXBMCApp::StartActivity Failed to load %s. Exception follows:", package.c_str());
779       env->ExceptionDescribe();
780       env->ExceptionClear();
781       env->DeleteLocalRef(cActivity);
782       DetachCurrentThread();
783       return false;
784     }
785     if (!oIntent)
786     {
787       CLog::Log(LOGERROR, "CXBMCApp::StartActivity %s has no Launch Intent", package.c_str());
788       env->DeleteLocalRef(cActivity);
789       DetachCurrentThread();
790       return false;
791     }
792   }
793
794   jobject oUri;
795   if (dataURI.size())
796   {
797     // Java equivalent for the following JNI
798     //   Uri oUri = Uri.parse(sPath);
799     jclass cUri = env->FindClass("android/net/Uri");
800     jmethodID midUriParse = env->GetStaticMethodID(cUri, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
801     jstring sPath = env->NewStringUTF(dataURI.c_str());
802     oUri = env->CallStaticObjectMethod(cUri, midUriParse, sPath);
803     env->DeleteLocalRef(sPath);
804     env->DeleteLocalRef(cUri);
805
806     // Run setData or setDataAndType depending on what was passed into the method
807     //   This allows opening market links or external players using the same method
808     if (dataType.size())
809     {
810       // Java equivalent for the following JNI
811       //   oIntent.setDataAndType(oUri, "video/*");
812       jmethodID midIntentSetDataAndType = env->GetMethodID(cIntent, "setDataAndType", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/Intent;");
813       jstring sMimeType = env->NewStringUTF(dataType.c_str());
814       oIntent = env->CallObjectMethod(oIntent, midIntentSetDataAndType, oUri, sMimeType);
815       env->DeleteLocalRef(sMimeType);
816     }
817     else 
818     {
819       // Java equivalent for the following JNI
820       //   oIntent.setData(oUri);
821       jmethodID midIntentSetData = env->GetMethodID(cIntent, "setData", "(Landroid/net/Uri;)Landroid/content/Intent;");
822       oIntent = env->CallObjectMethod(oIntent, midIntentSetData, oUri);
823     }
824   }
825   
826   // Java equivalent for the following JNI
827   //   oIntent.setPackage(sPackage);
828   jstring sPackage = env->NewStringUTF(package.c_str());
829   jmethodID mSetPackage = env->GetMethodID(cIntent, "setPackage", "(Ljava/lang/String;)Landroid/content/Intent;");
830   oIntent = env->CallObjectMethod(oIntent, mSetPackage, sPackage);
831
832   if (oUri != NULL)
833   {
834     env->DeleteLocalRef(oUri);
835   }
836   env->DeleteLocalRef(cIntent);
837   env->DeleteLocalRef(sPackage);
838  
839   // Java equivalent for the following JNI
840   //   startActivity(oIntent);
841   jmethodID mStartActivity = env->GetMethodID(cActivity, "startActivity", "(Landroid/content/Intent;)V");
842   env->CallVoidMethod(oActivity, mStartActivity, oIntent);
843   env->DeleteLocalRef(cActivity);
844   env->DeleteLocalRef(oIntent);
845
846   exc = env->ExceptionOccurred();
847   if (exc)
848   {
849     CLog::Log(LOGERROR, "CXBMCApp::StartActivity Failed to load %s. Exception follows:", package.c_str());
850     env->ExceptionDescribe();
851     env->ExceptionClear();
852     DetachCurrentThread();
853     return false;
854   }
855
856   DetachCurrentThread();
857   return true;
858 }
859
860 int CXBMCApp::GetBatteryLevel()
861 {
862   if (m_activity == NULL)
863     return -1;
864
865   JNIEnv *env = NULL;
866   AttachCurrentThread(&env);
867   jobject oActivity = m_activity->clazz;
868
869   // IntentFilter oIntentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
870   jclass cIntentFilter = env->FindClass("android/content/IntentFilter");
871   jmethodID midIntentFilterCtor = env->GetMethodID(cIntentFilter, "<init>", "(Ljava/lang/String;)V");
872   jstring sIntentBatteryChanged = env->NewStringUTF("android.intent.action.BATTERY_CHANGED"); // Intent.ACTION_BATTERY_CHANGED
873   jobject oIntentFilter = env->NewObject(cIntentFilter, midIntentFilterCtor, sIntentBatteryChanged);
874   env->DeleteLocalRef(cIntentFilter);
875   env->DeleteLocalRef(sIntentBatteryChanged);
876
877   // Intent oBatteryStatus = activity.registerReceiver(null, oIntentFilter);
878   jclass cActivity = env->GetObjectClass(oActivity);
879   jmethodID midActivityRegisterReceiver = env->GetMethodID(cActivity, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
880   env->DeleteLocalRef(cActivity);
881   jobject oBatteryStatus = env->CallObjectMethod(oActivity, midActivityRegisterReceiver, NULL, oIntentFilter);
882
883   jclass cIntent = env->GetObjectClass(oBatteryStatus);
884   jmethodID midIntentGetIntExtra = env->GetMethodID(cIntent, "getIntExtra", "(Ljava/lang/String;I)I");
885   env->DeleteLocalRef(cIntent);
886   
887   // int iLevel = oBatteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
888   jstring sBatteryManagerExtraLevel = env->NewStringUTF("level"); // BatteryManager.EXTRA_LEVEL
889   jint iLevel = env->CallIntMethod(oBatteryStatus, midIntentGetIntExtra, sBatteryManagerExtraLevel, (jint)-1);
890   env->DeleteLocalRef(sBatteryManagerExtraLevel);
891   // int iScale = oBatteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
892   jstring sBatteryManagerExtraScale = env->NewStringUTF("scale"); // BatteryManager.EXTRA_SCALE
893   jint iScale = env->CallIntMethod(oBatteryStatus, midIntentGetIntExtra, sBatteryManagerExtraScale, (jint)-1);
894   env->DeleteLocalRef(sBatteryManagerExtraScale);
895   env->DeleteLocalRef(oBatteryStatus);
896   env->DeleteLocalRef(oIntentFilter);
897
898   DetachCurrentThread();
899
900   if (iLevel <= 0 || iScale < 0)
901     return iLevel;
902
903   return ((int)iLevel * 100) / (int)iScale;
904 }
905
906 bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
907 {
908   if (m_activity == NULL)
909     return false;
910
911   JNIEnv *env = NULL;
912   AttachCurrentThread(&env);
913
914   // check if external storage is available
915   // String sStorageState = android.os.Environment.getExternalStorageState();
916   jclass cEnvironment = env->FindClass("android/os/Environment");
917   jmethodID midEnvironmentGetExternalStorageState = env->GetStaticMethodID(cEnvironment, "getExternalStorageState", "()Ljava/lang/String;");
918   jstring sStorageState = (jstring)env->CallStaticObjectMethod(cEnvironment, midEnvironmentGetExternalStorageState);
919   // if (sStorageState != Environment.MEDIA_MOUNTED && sStorageState != Environment.MEDIA_MOUNTED_READ_ONLY) return false;
920   const char* storageState = env->GetStringUTFChars(sStorageState, NULL);
921   bool mounted = strcmp(storageState, "mounted") == 0 || strcmp(storageState, "mounted_ro") == 0;
922   env->ReleaseStringUTFChars(sStorageState, storageState);
923   env->DeleteLocalRef(sStorageState);
924
925   if (mounted)
926   {
927     jobject oExternalStorageDirectory = NULL;
928     if (type.empty() || type == "files")
929     {
930       // File oExternalStorageDirectory = Environment.getExternalStorageDirectory();
931       jmethodID midEnvironmentGetExternalStorageDirectory = env->GetStaticMethodID(cEnvironment, "getExternalStorageDirectory", "()Ljava/io/File;");
932       oExternalStorageDirectory = env->CallStaticObjectMethod(cEnvironment, midEnvironmentGetExternalStorageDirectory);
933     }
934     else if (type == "music" || type == "videos" || type == "pictures" || type == "photos" || type == "downloads")
935     {
936       jstring sType = NULL;
937       if (type == "music")
938         sType = env->NewStringUTF("Music"); // Environment.DIRECTORY_MUSIC
939       else if (type == "videos")
940         sType = env->NewStringUTF("Movies"); // Environment.DIRECTORY_MOVIES
941       else if (type == "pictures")
942         sType = env->NewStringUTF("Pictures"); // Environment.DIRECTORY_PICTURES
943       else if (type == "photos")
944         sType = env->NewStringUTF("DCIM"); // Environment.DIRECTORY_DCIM
945       else if (type == "downloads")
946         sType = env->NewStringUTF("Download"); // Environment.DIRECTORY_DOWNLOADS
947
948       // File oExternalStorageDirectory = Environment.getExternalStoragePublicDirectory(sType);
949       jmethodID midEnvironmentGetExternalStoragePublicDirectory = env->GetStaticMethodID(cEnvironment, "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;");
950       oExternalStorageDirectory = env->CallStaticObjectMethod(cEnvironment, midEnvironmentGetExternalStoragePublicDirectory, sType);
951       env->DeleteLocalRef(sType);
952     }
953
954     if (oExternalStorageDirectory != NULL)
955     {
956       // path = oExternalStorageDirectory.getAbsolutePath();
957       jclass cFile = env->GetObjectClass(oExternalStorageDirectory);
958       jmethodID midFileGetAbsolutePath = env->GetMethodID(cFile, "getAbsolutePath", "()Ljava/lang/String;");
959       env->DeleteLocalRef(cFile);
960       jstring sPath = (jstring)env->CallObjectMethod(oExternalStorageDirectory, midFileGetAbsolutePath);
961       const char* cPath = env->GetStringUTFChars(sPath, NULL);
962       path = cPath;
963       env->ReleaseStringUTFChars(sPath, cPath);
964       env->DeleteLocalRef(sPath);
965       env->DeleteLocalRef(oExternalStorageDirectory);
966     }
967     else
968       mounted = false;
969   }
970
971   env->DeleteLocalRef(cEnvironment);
972
973   DetachCurrentThread();
974
975   return mounted && !path.empty();
976 }
977
978 bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage)
979 {
980   if (m_activity == NULL)
981     return false;
982
983   if (path.empty())
984   {
985     ostringstream fmt;
986     fmt.width(24);  fmt << left  << "Filesystem";
987     fmt.width(12);  fmt << right << "Size";
988     fmt.width(12);  fmt << "Used";
989     fmt.width(12);  fmt << "Avail";
990     fmt.width(12);  fmt << "Use %";
991
992     usage = fmt.str();
993     return false;
994   }
995
996   JNIEnv *env = NULL;
997   AttachCurrentThread(&env);
998
999   // android.os.StatFs oStats = new android.os.StatFs(sPath);
1000   jclass cStatFs = env->FindClass("android/os/StatFs");
1001   jmethodID midStatFsCtor = env->GetMethodID(cStatFs, "<init>", "(Ljava/lang/String;)V");
1002   jstring sPath = env->NewStringUTF(path.c_str());
1003   jobject oStats = env->NewObject(cStatFs, midStatFsCtor, sPath);
1004   env->DeleteLocalRef(sPath);
1005
1006   // int iBlockSize = oStats.getBlockSize();
1007   jmethodID midStatFsGetBlockSize = env->GetMethodID(cStatFs, "getBlockSize", "()I");
1008   jint iBlockSize = env->CallIntMethod(oStats, midStatFsGetBlockSize);
1009   
1010   // int iBlocksTotal = oStats.getBlockCount();
1011   jmethodID midStatFsGetBlockCount = env->GetMethodID(cStatFs, "getBlockCount", "()I");
1012   jint iBlocksTotal = env->CallIntMethod(oStats, midStatFsGetBlockCount);
1013   
1014   // int iBlocksFree = oStats.getFreeBlocks();
1015   jmethodID midStatFsGetFreeBlocks = env->GetMethodID(cStatFs, "getFreeBlocks", "()I");
1016   jint iBlocksFree = env->CallIntMethod(oStats, midStatFsGetFreeBlocks);
1017
1018   env->DeleteLocalRef(oStats);
1019   env->DeleteLocalRef(cStatFs);
1020
1021   DetachCurrentThread();
1022
1023   if (iBlockSize <= 0 || iBlocksTotal <= 0 || iBlocksFree < 0)
1024     return false;
1025   
1026   float totalSize = (float)iBlockSize * iBlocksTotal / GIGABYTES;
1027   float freeSize = (float)iBlockSize * iBlocksFree / GIGABYTES;
1028   float usedSize = totalSize - freeSize;
1029   float usedPercentage = usedSize / totalSize * 100;
1030
1031   ostringstream fmt;
1032   fmt << fixed;
1033   fmt.precision(1);
1034   fmt.width(24);  fmt << left  << path;
1035   fmt.width(12);  fmt << right << totalSize << "G"; // size in GB
1036   fmt.width(12);  fmt << usedSize << "G"; // used in GB
1037   fmt.width(12);  fmt << freeSize << "G"; // free
1038   fmt.precision(0);
1039   fmt.width(12);  fmt << usedPercentage << "%"; // percentage used
1040   
1041   usage = fmt.str();
1042   return true;
1043 }
1044
1045 // Used in Application.cpp to figure out volume steps
1046 int CXBMCApp::GetMaxSystemVolume()
1047 {
1048   static int maxVolume = -1;
1049   if (maxVolume == -1)
1050   {
1051     JNIEnv *env = NULL;
1052     AttachCurrentThread(&env);
1053     maxVolume = GetMaxSystemVolume(env);
1054     DetachCurrentThread();
1055   }
1056   return maxVolume;
1057 }
1058
1059 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
1060 {
1061   jobject oActivity = m_activity->clazz;
1062   jclass cActivity = env->GetObjectClass(oActivity);
1063
1064   // Get Audio manager
1065   //  (AudioManager)getSystemService(Context.AUDIO_SERVICE)
1066   jmethodID mgetSystemService = env->GetMethodID(cActivity, "getSystemService","(Ljava/lang/String;)Ljava/lang/Object;");
1067   jstring sAudioService = env->NewStringUTF("audio");
1068   jobject oAudioManager = env->CallObjectMethod(oActivity, mgetSystemService, sAudioService);
1069   env->DeleteLocalRef(sAudioService);
1070   env->DeleteLocalRef(cActivity);
1071
1072   // Get max volume
1073   //  int max_volume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
1074   jclass cAudioManager = env->GetObjectClass(oAudioManager);
1075   jmethodID mgetStreamMaxVolume = env->GetMethodID(cAudioManager, "getStreamMaxVolume", "(I)I");
1076   jfieldID fstreamMusic = env->GetStaticFieldID(cAudioManager, "STREAM_MUSIC", "I");
1077   jint stream_music = env->GetStaticIntField(cAudioManager, fstreamMusic);
1078   int maxVolume = (int)env->CallObjectMethod(oAudioManager, mgetStreamMaxVolume, stream_music); // AudioManager.STREAM_MUSIC
1079
1080   env->DeleteLocalRef(oAudioManager);
1081   env->DeleteLocalRef(cAudioManager);
1082
1083   return maxVolume;
1084 }
1085
1086 void CXBMCApp::SetSystemVolume(JNIEnv *env, float percent)
1087 {
1088   CLog::Log(LOGDEBUG, "CXBMCApp::SetSystemVolume: %f", percent);
1089
1090   jobject oActivity = m_activity->clazz;
1091   jclass cActivity = env->GetObjectClass(oActivity);
1092
1093   // Get Audio manager
1094   //  (AudioManager)getSystemService(Context.AUDIO_SERVICE)
1095   jmethodID mgetSystemService = env->GetMethodID(cActivity, "getSystemService","(Ljava/lang/String;)Ljava/lang/Object;");
1096   jstring sAudioService = env->NewStringUTF("audio");
1097   jobject oAudioManager = env->CallObjectMethod(oActivity, mgetSystemService, sAudioService);
1098   jclass cAudioManager = env->GetObjectClass(oAudioManager);
1099   env->DeleteLocalRef(sAudioService);
1100   env->DeleteLocalRef(cActivity);
1101
1102   // Set volume
1103   //   mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, max_volume, 0);
1104   jfieldID fstreamMusic = env->GetStaticFieldID(cAudioManager, "STREAM_MUSIC", "I");
1105   jint stream_music = env->GetStaticIntField(cAudioManager, fstreamMusic);
1106   jmethodID msetStreamVolume = env->GetMethodID(cAudioManager, "setStreamVolume", "(III)V");
1107   env->CallObjectMethod(oAudioManager, msetStreamVolume, stream_music, int(GetMaxSystemVolume(env)*percent), 0);
1108   env->DeleteLocalRef(oAudioManager);
1109   env->DeleteLocalRef(cAudioManager);
1110 }
1111