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