Merge pull request #3437 from Memphiz/iossandbox
[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 = GetPackageManager().getLaunchIntentForPackage(package);
405   if (!newIntent)
406     return false;
407
408   if (!dataURI.empty())
409     newIntent.setData(dataURI);
410
411   if (!intent.empty())
412     newIntent.setAction(intent);
413
414    startActivity(newIntent);
415   return true;
416 }
417
418 int CXBMCApp::GetBatteryLevel()
419 {
420   return m_batteryLevel;
421 }
422
423 bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
424 {
425   std::string sType;
426   std::string mountedState;
427   bool mounted = false;
428
429   if(type == "files" || type.empty())
430   {
431     CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
432     if (external)
433       path = external.getAbsolutePath();
434   }
435   else
436   {
437     if (type == "music")
438       sType = "Music"; // Environment.DIRECTORY_MUSIC
439     else if (type == "videos")
440       sType = "Movies"; // Environment.DIRECTORY_MOVIES
441     else if (type == "pictures")
442       sType = "Pictures"; // Environment.DIRECTORY_PICTURES
443     else if (type == "photos")
444       sType = "DCIM"; // Environment.DIRECTORY_DCIM
445     else if (type == "downloads")
446       sType = "Download"; // Environment.DIRECTORY_DOWNLOADS
447     if (!sType.empty())
448     {
449       CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
450       if (external)
451         path = external.getAbsolutePath();
452     }
453   }
454   mountedState = CJNIEnvironment::getExternalStorageState();
455   mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
456   return mounted && !path.empty();
457 }
458
459 bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage)
460 {
461   if (path.empty())
462   {
463     std::ostringstream fmt;
464     fmt.width(24);  fmt << std::left  << "Filesystem";
465     fmt.width(12);  fmt << std::right << "Size";
466     fmt.width(12);  fmt << "Used";
467     fmt.width(12);  fmt << "Avail";
468     fmt.width(12);  fmt << "Use %";
469
470     usage = fmt.str();
471     return false;
472   }
473
474   CJNIStatFs fileStat(path);
475   int blockSize = fileStat.getBlockSize();
476   int blockCount = fileStat.getBlockCount();
477   int freeBlocks = fileStat.getFreeBlocks();
478
479   if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0)
480     return false;
481
482   float totalSize = (float)blockSize * blockCount / GIGABYTES;
483   float freeSize = (float)blockSize * freeBlocks / GIGABYTES;
484   float usedSize = totalSize - freeSize;
485   float usedPercentage = usedSize / totalSize * 100;
486
487   std::ostringstream fmt;
488   fmt << std::fixed;
489   fmt.precision(1);
490   fmt.width(24);  fmt << std::left  << path;
491   fmt.width(12);  fmt << std::right << totalSize << "G"; // size in GB
492   fmt.width(12);  fmt << usedSize << "G"; // used in GB
493   fmt.width(12);  fmt << freeSize << "G"; // free
494   fmt.precision(0);
495   fmt.width(12);  fmt << usedPercentage << "%"; // percentage used
496
497   usage = fmt.str();
498   return true;
499 }
500
501 // Used in Application.cpp to figure out volume steps
502 int CXBMCApp::GetMaxSystemVolume()
503 {
504   JNIEnv* env = xbmc_jnienv();
505   static int maxVolume = -1;
506   if (maxVolume == -1)
507   {
508     maxVolume = GetMaxSystemVolume(env);
509   }
510   //android_printf("CXBMCApp::GetMaxSystemVolume: %i",maxVolume);
511   return maxVolume;
512 }
513
514 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
515 {
516   CJNIAudioManager audioManager(getSystemService("audio"));
517   if (audioManager)
518     return audioManager.getStreamMaxVolume();
519   android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
520   return 0;
521 }
522
523 int CXBMCApp::GetSystemVolume()
524 {
525   CJNIAudioManager audioManager(getSystemService("audio"));
526   if (audioManager)
527     return audioManager.getStreamVolume();
528   else 
529   {
530     android_printf("CXBMCApp::GetSystemVolume: Could not get Audio Manager");
531     return 0;
532   }
533 }
534
535 void CXBMCApp::SetSystemVolume(int val)
536 {
537   CJNIAudioManager audioManager(getSystemService("audio"));
538   if (audioManager)
539     audioManager.setStreamVolume(val);
540   else
541     android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
542 }
543
544 void CXBMCApp::SetSystemVolume(JNIEnv *env, float percent)
545 {
546   CJNIAudioManager audioManager(getSystemService("audio"));
547   int maxVolume = (int)(GetMaxSystemVolume() * percent);
548   if (audioManager)
549     audioManager.setStreamVolume(maxVolume);
550   else
551     android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
552 }
553
554 void CXBMCApp::onReceive(CJNIIntent intent)
555 {
556   std::string action = intent.getAction();
557   android_printf("CXBMCApp::onReceive Got intent. Action: %s", action.c_str());
558   if (action == "android.intent.action.BATTERY_CHANGED")
559     m_batteryLevel = intent.getIntExtra("level",-1);
560 }
561
562 void CXBMCApp::onNewIntent(CJNIIntent intent)
563 {
564   std::string action = intent.getAction();
565   if (action == "android.intent.action.VIEW")
566   {
567     std::string playFile = GetFilenameFromIntent(intent);
568     CApplicationMessenger::Get().MediaPlay(playFile);
569   }
570 }
571
572 void CXBMCApp::SetupEnv()
573 {
574   setenv("XBMC_ANDROID_SYSTEM_LIBS", CJNISystem::getProperty("java.library.path").c_str(), 0);
575   setenv("XBMC_ANDROID_DATA", getApplicationInfo().dataDir.c_str(), 0);
576   setenv("XBMC_ANDROID_LIBS", getApplicationInfo().nativeLibraryDir.c_str(), 0);
577   setenv("XBMC_ANDROID_APK", getPackageResourcePath().c_str(), 0);
578
579   std::string cacheDir = getCacheDir().getAbsolutePath();
580   setenv("XBMC_BIN_HOME", (cacheDir + "/apk/assets").c_str(), 0);
581   setenv("XBMC_HOME", (cacheDir + "/apk/assets").c_str(), 0);
582
583   std::string externalDir;
584   CJNIFile androidPath = getExternalFilesDir("");
585   if (!androidPath)
586     androidPath = getDir("org.xbmc.xbmc", 1);
587
588   if (androidPath)
589     externalDir = androidPath.getAbsolutePath();
590
591   if (!externalDir.empty())
592     setenv("HOME", externalDir.c_str(), 0);
593   else
594     setenv("HOME", getenv("XBMC_TEMP"), 0);
595 }
596
597 std::string CXBMCApp::GetFilenameFromIntent(const CJNIIntent &intent)
598 {
599     std::string ret;
600     if (!intent)
601       return ret;
602     CJNIURI data = intent.getData();
603     if (!data)
604       return ret;
605     std::string scheme = data.getScheme();
606     StringUtils::ToLower(scheme);
607     if (scheme == "content")
608     {
609       std::vector<std::string> filePathColumn;
610       filePathColumn.push_back(CJNIMediaStoreMediaColumns::DATA);
611       CJNICursor cursor = getContentResolver().query(data, filePathColumn, std::string(), std::vector<std::string>(), std::string());
612       if(cursor.moveToFirst())
613       {
614         int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
615         ret = cursor.getString(columnIndex);
616       }
617       cursor.close();
618     }
619     else if(scheme == "file")
620       ret = data.getPath();
621     else
622       ret = data.toString();
623   return ret;
624 }
625
626 const ANativeWindow** CXBMCApp::GetNativeWindow(int timeout)
627 {
628   if (m_window)
629     return (const ANativeWindow**)&m_window;
630
631   m_windowCreated.WaitMSec(timeout);
632   return (const ANativeWindow**)&m_window;
633 }