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