Merge pull request #5095 from koying/fixdroidappcrash
[vuplus_xbmc] / xbmc / android / activity / XBMCApp.cpp
1 /*
2  *      Copyright (C) 2012-2013 Team XBMC
3  *      http://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 #include "utils/StringUtils.h"
48 #include "AppParamParser.h"
49 #include "XbmcContext.h"
50 #include <android/bitmap.h>
51 #include "android/jni/JNIThreading.h"
52 #include "android/jni/BroadcastReceiver.h"
53 #include "android/jni/Intent.h"
54 #include "android/jni/PackageManager.h"
55 #include "android/jni/Context.h"
56 #include "android/jni/AudioManager.h"
57 #include "android/jni/PowerManager.h"
58 #include "android/jni/WakeLock.h"
59 #include "android/jni/Environment.h"
60 #include "android/jni/File.h"
61 #include "android/jni/IntentFilter.h"
62 #include "android/jni/NetworkInfo.h"
63 #include "android/jni/ConnectivityManager.h"
64 #include "android/jni/System.h"
65 #include "android/jni/ApplicationInfo.h"
66 #include "android/jni/StatFs.h"
67 #include "android/jni/BitmapDrawable.h"
68 #include "android/jni/Bitmap.h"
69 #include "android/jni/CharSequence.h"
70 #include "android/jni/URI.h"
71 #include "android/jni/Cursor.h"
72 #include "android/jni/ContentResolver.h"
73 #include "android/jni/MediaStore.h"
74
75 #define GIGABYTES       1073741824
76
77 using namespace std;
78
79 template<class T, void(T::*fn)()>
80 void* thread_run(void* obj)
81 {
82   (static_cast<T*>(obj)->*fn)();
83   return NULL;
84 }
85 CEvent CXBMCApp::m_windowCreated;
86 ANativeActivity *CXBMCApp::m_activity = NULL;
87 ANativeWindow* CXBMCApp::m_window = NULL;
88 int CXBMCApp::m_batteryLevel = 0;
89 int CXBMCApp::m_initialVolume = 0;
90 CCriticalSection CXBMCApp::m_applicationsMutex;
91 std::vector<androidPackage> CXBMCApp::m_applications;
92
93
94 CXBMCApp::CXBMCApp(ANativeActivity* nativeActivity)
95   : CJNIContext(nativeActivity)
96   , CJNIBroadcastReceiver("org/xbmc/xbmc/XBMCBroadcastReceiver")
97   , m_wakeLock(NULL)
98 {
99   m_activity = nativeActivity;
100   m_firstrun = true;
101   m_exiting=false;
102   if (m_activity == NULL)
103   {
104     android_printf("CXBMCApp: invalid ANativeActivity instance");
105     exit(1);
106     return;
107   }
108 }
109
110 CXBMCApp::~CXBMCApp()
111 {
112 }
113
114 void CXBMCApp::onStart()
115 {
116   android_printf("%s: ", __PRETTY_FUNCTION__);
117   if (!m_firstrun)
118   {
119     android_printf("%s: Already running, ignoring request to start", __PRETTY_FUNCTION__);
120     return;
121   }
122   pthread_attr_t attr;
123   pthread_attr_init(&attr);
124   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
125   pthread_create(&m_thread, &attr, thread_run<CXBMCApp, &CXBMCApp::run>, this);
126   pthread_attr_destroy(&attr);
127 }
128
129 void CXBMCApp::onResume()
130 {
131   android_printf("%s: ", __PRETTY_FUNCTION__);
132   CJNIIntentFilter batteryFilter;
133   batteryFilter.addAction("android.intent.action.BATTERY_CHANGED");
134   registerReceiver(*this, batteryFilter);
135
136   // Clear the applications cache. We could have installed/deinstalled apps
137   {
138     CSingleLock lock(m_applicationsMutex);
139     m_applications.clear();
140   }
141 }
142
143 void CXBMCApp::onPause()
144 {
145   android_printf("%s: ", __PRETTY_FUNCTION__);
146
147   // Restore volume
148   SetSystemVolume(m_initialVolume);
149
150   unregisterReceiver(*this);
151 }
152
153 void CXBMCApp::onStop()
154 {
155   android_printf("%s: ", __PRETTY_FUNCTION__);
156 }
157
158 void CXBMCApp::onDestroy()
159 {
160   android_printf("%s", __PRETTY_FUNCTION__);
161
162   // If android is forcing us to stop, ask XBMC to exit then wait until it's
163   // been destroyed.
164   if (!m_exiting)
165   {
166     XBMC_Stop();
167     pthread_join(m_thread, NULL);
168     android_printf(" => XBMC finished");
169   }
170
171   if (m_wakeLock != NULL && m_activity != NULL)
172   {
173     delete m_wakeLock;
174     m_wakeLock = NULL;
175   }
176 }
177
178 void CXBMCApp::onSaveState(void **data, size_t *size)
179 {
180   android_printf("%s: ", __PRETTY_FUNCTION__);
181   // no need to save anything as XBMC is running in its own thread
182 }
183
184 void CXBMCApp::onConfigurationChanged()
185 {
186   android_printf("%s: ", __PRETTY_FUNCTION__);
187   // ignore any configuration changes like screen rotation etc
188 }
189
190 void CXBMCApp::onLowMemory()
191 {
192   android_printf("%s: ", __PRETTY_FUNCTION__);
193   // can't do much as we don't want to close completely
194 }
195
196 void CXBMCApp::onCreateWindow(ANativeWindow* window)
197 {
198   android_printf("%s: ", __PRETTY_FUNCTION__);
199   if (window == NULL)
200   {
201     android_printf(" => invalid ANativeWindow object");
202     return;
203   }
204   m_window = window;
205   m_windowCreated.Set();
206   if (getWakeLock() &&  m_wakeLock)
207     m_wakeLock->acquire();
208   if(!m_firstrun)
209   {
210     XBMC_SetupDisplay();
211     XBMC_Pause(false);
212   }
213 }
214
215 void CXBMCApp::onResizeWindow()
216 {
217   android_printf("%s: ", __PRETTY_FUNCTION__);
218   m_window=NULL;
219   m_windowCreated.Reset();
220   // no need to do anything because we are fixed in fullscreen landscape mode
221 }
222
223 void CXBMCApp::onDestroyWindow()
224 {
225   android_printf("%s: ", __PRETTY_FUNCTION__);
226
227   // If we have exited XBMC, it no longer exists.
228   if (!m_exiting)
229   {
230     XBMC_DestroyDisplay();
231     XBMC_Pause(true);
232   }
233
234   if (m_wakeLock)
235     m_wakeLock->release();
236
237 }
238
239 void CXBMCApp::onGainFocus()
240 {
241   android_printf("%s: ", __PRETTY_FUNCTION__);
242 }
243
244 void CXBMCApp::onLostFocus()
245 {
246   android_printf("%s: ", __PRETTY_FUNCTION__);
247 }
248
249 bool CXBMCApp::getWakeLock()
250 {
251   if (m_wakeLock)
252     return true;
253
254   m_wakeLock = new CJNIWakeLock(CJNIPowerManager(getSystemService("power")).newWakeLock("org.xbmc.xbmc"));
255
256   return true;
257 }
258
259 void CXBMCApp::run()
260 {
261   int status = 0;
262
263   SetupEnv();
264   XBMC::Context context;
265
266   m_initialVolume = GetSystemVolume();
267
268   CJNIIntent startIntent = getIntent();
269   android_printf("XBMC Started with action: %s\n",startIntent.getAction().c_str());
270
271   std::string filenameToPlay = GetFilenameFromIntent(startIntent);
272   if (!filenameToPlay.empty())
273   {
274     int argc = 2;
275     const char** argv = (const char**) malloc(argc*sizeof(char*));
276
277     std::string exe_name("XBMC");
278     argv[0] = exe_name.c_str();
279     argv[1] = filenameToPlay.c_str();
280
281     CAppParamParser appParamParser;
282     appParamParser.Parse((const char **)argv, argc);
283
284     free(argv);
285   }
286
287   m_firstrun=false;
288   android_printf(" => running XBMC_Run...");
289   try
290   {
291     status = XBMC_Run(true);
292     android_printf(" => XBMC_Run finished with %d", status);
293   }
294   catch(...)
295   {
296     android_printf("ERROR: Exception caught on main loop. Exiting");
297   }
298
299   // If we are have not been force by Android to exit, notify its finish routine.
300   // This will cause android to run through its teardown events, it calls:
301   // onPause(), onLostFocus(), onDestroyWindow(), onStop(), onDestroy().
302   ANativeActivity_finish(m_activity);
303   m_exiting=true;
304 }
305
306 void CXBMCApp::XBMC_Pause(bool pause)
307 {
308   android_printf("XBMC_Pause(%s)", pause ? "true" : "false");
309   // Only send the PAUSE action if we are pausing XBMC and something is currently playing
310   if (pause && g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused())
311     CApplicationMessenger::Get().SendAction(CAction(ACTION_PAUSE), WINDOW_INVALID, true);
312 }
313
314 void CXBMCApp::XBMC_Stop()
315 {
316   CApplicationMessenger::Get().Quit();
317 }
318
319 bool CXBMCApp::XBMC_SetupDisplay()
320 {
321   android_printf("XBMC_SetupDisplay()");
322   return CApplicationMessenger::Get().SetupDisplay();
323 }
324
325 bool CXBMCApp::XBMC_DestroyDisplay()
326 {
327   android_printf("XBMC_DestroyDisplay()");
328   return CApplicationMessenger::Get().DestroyDisplay();
329 }
330
331 int CXBMCApp::SetBuffersGeometry(int width, int height, int format)
332 {
333   return ANativeWindow_setBuffersGeometry(m_window, width, height, format);
334 }
335
336 int CXBMCApp::android_printf(const char *format, ...)
337 {
338   // For use before CLog is setup by XBMC_Run()
339   va_list args;
340   va_start(args, format);
341   int result = __android_log_vprint(ANDROID_LOG_VERBOSE, "XBMC", format, args);
342   va_end(args);
343   return result;
344 }
345
346 int CXBMCApp::GetDPI()
347 {
348   if (m_activity == NULL || m_activity->assetManager == NULL)
349     return 0;
350
351   // grab DPI from the current configuration - this is approximate
352   // but should be close enough for what we need
353   AConfiguration *config = AConfiguration_new();
354   AConfiguration_fromAssetManager(config, m_activity->assetManager);
355   int dpi = AConfiguration_getDensity(config);
356   AConfiguration_delete(config);
357
358   return dpi;
359 }
360
361 std::vector<androidPackage> CXBMCApp::GetApplications()
362 {
363   CSingleLock lock(m_applicationsMutex);
364   if (m_applications.empty())
365   {
366     CJNIList<CJNIApplicationInfo> packageList = GetPackageManager().getInstalledApplications(CJNIPackageManager::GET_ACTIVITIES);
367     int numPackages = packageList.size();
368     for (int i = 0; i < numPackages; i++)
369     {
370       androidPackage newPackage;
371       newPackage.packageName = packageList.get(i).packageName;
372       newPackage.packageLabel = GetPackageManager().getApplicationLabel(packageList.get(i)).toString();
373       CJNIIntent intent = GetPackageManager().getLaunchIntentForPackage(newPackage.packageName);
374       if (!intent || !intent.hasCategory("android.intent.category.LAUNCHER"))
375         continue;
376
377       m_applications.push_back(newPackage);
378     }
379   }
380
381   return m_applications;
382 }
383
384 bool CXBMCApp::GetIconSize(const string &packageName, int *width, int *height)
385 {
386   JNIEnv* env = xbmc_jnienv();
387   AndroidBitmapInfo info;
388   CJNIBitmapDrawable drawable = (CJNIBitmapDrawable)GetPackageManager().getApplicationIcon(packageName);
389   CJNIBitmap icon(drawable.getBitmap());
390   AndroidBitmap_getInfo(env, icon.get_raw(), &info);
391   *width = info.width;
392   *height = info.height;
393   return true;
394 }
395
396 bool CXBMCApp::GetIcon(const string &packageName, void* buffer, unsigned int bufSize)
397 {
398   void *bitmapBuf = NULL;
399   JNIEnv* env = xbmc_jnienv();
400   CJNIBitmapDrawable drawable = (CJNIBitmapDrawable)GetPackageManager().getApplicationIcon(packageName);
401   CJNIBitmap bitmap(drawable.getBitmap());
402   AndroidBitmap_lockPixels(env, bitmap.get_raw(), &bitmapBuf);
403   if (bitmapBuf)
404   {
405     memcpy(buffer, bitmapBuf, bufSize);
406     AndroidBitmap_unlockPixels(env, bitmap.get_raw());
407     return true;
408   }
409   return false;
410 }
411
412 bool CXBMCApp::HasLaunchIntent(const string &package)
413 {
414   return GetPackageManager().getLaunchIntentForPackage(package) != NULL;
415 }
416
417 // Note intent, dataType, dataURI all default to ""
418 bool CXBMCApp::StartActivity(const string &package, const string &intent, const string &dataType, const string &dataURI)
419 {
420   CJNIIntent newIntent = intent.empty() ?
421     GetPackageManager().getLaunchIntentForPackage(package) :
422     CJNIIntent(intent);
423
424   if (!newIntent)
425     return false;
426
427   if (!dataURI.empty())
428   {
429     CJNIURI jniURI = CJNIURI::parse(dataURI);
430
431     if (!jniURI)
432       return false;
433
434     newIntent.setDataAndType(jniURI, dataType); 
435   }
436
437   newIntent.setPackage(package);
438   startActivity(newIntent);
439   if (xbmc_jnienv()->ExceptionOccurred())
440   {
441     CLog::Log(LOGERROR, "CXBMCApp::StartActivity - ExceptionOccurred launching %s", package.c_str());
442     xbmc_jnienv()->ExceptionClear();
443     return false;
444   }
445
446   return true;
447 }
448
449 int CXBMCApp::GetBatteryLevel()
450 {
451   return m_batteryLevel;
452 }
453
454 bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
455 {
456   std::string sType;
457   std::string mountedState;
458   bool mounted = false;
459
460   if(type == "files" || type.empty())
461   {
462     CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
463     if (external)
464       path = external.getAbsolutePath();
465   }
466   else
467   {
468     if (type == "music")
469       sType = "Music"; // Environment.DIRECTORY_MUSIC
470     else if (type == "videos")
471       sType = "Movies"; // Environment.DIRECTORY_MOVIES
472     else if (type == "pictures")
473       sType = "Pictures"; // Environment.DIRECTORY_PICTURES
474     else if (type == "photos")
475       sType = "DCIM"; // Environment.DIRECTORY_DCIM
476     else if (type == "downloads")
477       sType = "Download"; // Environment.DIRECTORY_DOWNLOADS
478     if (!sType.empty())
479     {
480       CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
481       if (external)
482         path = external.getAbsolutePath();
483     }
484   }
485   mountedState = CJNIEnvironment::getExternalStorageState();
486   mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
487   return mounted && !path.empty();
488 }
489
490 bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage)
491 {
492   if (path.empty())
493   {
494     std::ostringstream fmt;
495     fmt.width(24);  fmt << std::left  << "Filesystem";
496     fmt.width(12);  fmt << std::right << "Size";
497     fmt.width(12);  fmt << "Used";
498     fmt.width(12);  fmt << "Avail";
499     fmt.width(12);  fmt << "Use %";
500
501     usage = fmt.str();
502     return false;
503   }
504
505   CJNIStatFs fileStat(path);
506   int blockSize = fileStat.getBlockSize();
507   int blockCount = fileStat.getBlockCount();
508   int freeBlocks = fileStat.getFreeBlocks();
509
510   if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0)
511     return false;
512
513   float totalSize = (float)blockSize * blockCount / GIGABYTES;
514   float freeSize = (float)blockSize * freeBlocks / GIGABYTES;
515   float usedSize = totalSize - freeSize;
516   float usedPercentage = usedSize / totalSize * 100;
517
518   std::ostringstream fmt;
519   fmt << std::fixed;
520   fmt.precision(1);
521   fmt.width(24);  fmt << std::left  << path;
522   fmt.width(12);  fmt << std::right << totalSize << "G"; // size in GB
523   fmt.width(12);  fmt << usedSize << "G"; // used in GB
524   fmt.width(12);  fmt << freeSize << "G"; // free
525   fmt.precision(0);
526   fmt.width(12);  fmt << usedPercentage << "%"; // percentage used
527
528   usage = fmt.str();
529   return true;
530 }
531
532 // Used in Application.cpp to figure out volume steps
533 int CXBMCApp::GetMaxSystemVolume()
534 {
535   JNIEnv* env = xbmc_jnienv();
536   static int maxVolume = -1;
537   if (maxVolume == -1)
538   {
539     maxVolume = GetMaxSystemVolume(env);
540   }
541   //android_printf("CXBMCApp::GetMaxSystemVolume: %i",maxVolume);
542   return maxVolume;
543 }
544
545 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
546 {
547   CJNIAudioManager audioManager(getSystemService("audio"));
548   if (audioManager)
549     return audioManager.getStreamMaxVolume();
550   android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
551   return 0;
552 }
553
554 int CXBMCApp::GetSystemVolume()
555 {
556   CJNIAudioManager audioManager(getSystemService("audio"));
557   if (audioManager)
558     return audioManager.getStreamVolume();
559   else 
560   {
561     android_printf("CXBMCApp::GetSystemVolume: Could not get Audio Manager");
562     return 0;
563   }
564 }
565
566 void CXBMCApp::SetSystemVolume(int val)
567 {
568   CJNIAudioManager audioManager(getSystemService("audio"));
569   if (audioManager)
570     audioManager.setStreamVolume(val);
571   else
572     android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
573 }
574
575 void CXBMCApp::SetSystemVolume(JNIEnv *env, float percent)
576 {
577   CJNIAudioManager audioManager(getSystemService("audio"));
578   int maxVolume = (int)(GetMaxSystemVolume() * percent);
579   if (audioManager)
580     audioManager.setStreamVolume(maxVolume);
581   else
582     android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
583 }
584
585 void CXBMCApp::onReceive(CJNIIntent intent)
586 {
587   std::string action = intent.getAction();
588   android_printf("CXBMCApp::onReceive Got intent. Action: %s", action.c_str());
589   if (action == "android.intent.action.BATTERY_CHANGED")
590     m_batteryLevel = intent.getIntExtra("level",-1);
591 }
592
593 void CXBMCApp::onNewIntent(CJNIIntent intent)
594 {
595   std::string action = intent.getAction();
596   if (action == "android.intent.action.VIEW")
597   {
598     std::string playFile = GetFilenameFromIntent(intent);
599     CApplicationMessenger::Get().MediaPlay(playFile);
600   }
601 }
602
603 void CXBMCApp::SetupEnv()
604 {
605   setenv("XBMC_ANDROID_SYSTEM_LIBS", CJNISystem::getProperty("java.library.path").c_str(), 0);
606   setenv("XBMC_ANDROID_DATA", getApplicationInfo().dataDir.c_str(), 0);
607   setenv("XBMC_ANDROID_LIBS", getApplicationInfo().nativeLibraryDir.c_str(), 0);
608   setenv("XBMC_ANDROID_APK", getPackageResourcePath().c_str(), 0);
609
610   std::string cacheDir = getCacheDir().getAbsolutePath();
611   setenv("XBMC_BIN_HOME", (cacheDir + "/apk/assets").c_str(), 0);
612   setenv("XBMC_HOME", (cacheDir + "/apk/assets").c_str(), 0);
613
614   std::string externalDir;
615   CJNIFile androidPath = getExternalFilesDir("");
616   if (!androidPath)
617     androidPath = getDir("org.xbmc.xbmc", 1);
618
619   if (androidPath)
620     externalDir = androidPath.getAbsolutePath();
621
622   if (!externalDir.empty())
623     setenv("HOME", externalDir.c_str(), 0);
624   else
625     setenv("HOME", getenv("XBMC_TEMP"), 0);
626 }
627
628 std::string CXBMCApp::GetFilenameFromIntent(const CJNIIntent &intent)
629 {
630     std::string ret;
631     if (!intent)
632       return ret;
633     CJNIURI data = intent.getData();
634     if (!data)
635       return ret;
636     std::string scheme = data.getScheme();
637     StringUtils::ToLower(scheme);
638     if (scheme == "content")
639     {
640       std::vector<std::string> filePathColumn;
641       filePathColumn.push_back(CJNIMediaStoreMediaColumns::DATA);
642       CJNICursor cursor = getContentResolver().query(data, filePathColumn, std::string(), std::vector<std::string>(), std::string());
643       if(cursor.moveToFirst())
644       {
645         int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
646         ret = cursor.getString(columnIndex);
647       }
648       cursor.close();
649     }
650     else if(scheme == "file")
651       ret = data.getPath();
652     else
653       ret = data.toString();
654   return ret;
655 }
656
657 const ANativeWindow** CXBMCApp::GetNativeWindow(int timeout)
658 {
659   if (m_window)
660     return (const ANativeWindow**)&m_window;
661
662   m_windowCreated.WaitMSec(timeout);
663   return (const ANativeWindow**)&m_window;
664 }