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