2 * Copyright (C) 2012-2013 Team XBMC
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)
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.
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/>.
28 #include <android/native_window.h>
29 #include <android/configuration.h>
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>
40 #include "Application.h"
41 #include "settings/AdvancedSettings.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"
75 #define GIGABYTES 1073741824
79 template<class T, void(T::*fn)()>
80 void* thread_run(void* obj)
82 (static_cast<T*>(obj)->*fn)();
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;
94 CXBMCApp::CXBMCApp(ANativeActivity* nativeActivity)
95 : CJNIContext(nativeActivity)
96 , CJNIBroadcastReceiver("org/xbmc/xbmc/XBMCBroadcastReceiver")
99 m_activity = nativeActivity;
102 if (m_activity == NULL)
104 android_printf("CXBMCApp: invalid ANativeActivity instance");
110 CXBMCApp::~CXBMCApp()
114 void CXBMCApp::onStart()
116 android_printf("%s: ", __PRETTY_FUNCTION__);
119 android_printf("%s: Already running, ignoring request to start", __PRETTY_FUNCTION__);
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);
129 void CXBMCApp::onResume()
131 android_printf("%s: ", __PRETTY_FUNCTION__);
132 CJNIIntentFilter batteryFilter;
133 batteryFilter.addAction("android.intent.action.BATTERY_CHANGED");
134 registerReceiver(*this, batteryFilter);
136 // Clear the applications cache. We could have installed/deinstalled apps
138 CSingleLock lock(m_applicationsMutex);
139 m_applications.clear();
143 void CXBMCApp::onPause()
145 android_printf("%s: ", __PRETTY_FUNCTION__);
148 SetSystemVolume(m_initialVolume);
150 unregisterReceiver(*this);
153 void CXBMCApp::onStop()
155 android_printf("%s: ", __PRETTY_FUNCTION__);
158 void CXBMCApp::onDestroy()
160 android_printf("%s", __PRETTY_FUNCTION__);
162 // If android is forcing us to stop, ask XBMC to exit then wait until it's
167 pthread_join(m_thread, NULL);
168 android_printf(" => XBMC finished");
171 if (m_wakeLock != NULL && m_activity != NULL)
178 void CXBMCApp::onSaveState(void **data, size_t *size)
180 android_printf("%s: ", __PRETTY_FUNCTION__);
181 // no need to save anything as XBMC is running in its own thread
184 void CXBMCApp::onConfigurationChanged()
186 android_printf("%s: ", __PRETTY_FUNCTION__);
187 // ignore any configuration changes like screen rotation etc
190 void CXBMCApp::onLowMemory()
192 android_printf("%s: ", __PRETTY_FUNCTION__);
193 // can't do much as we don't want to close completely
196 void CXBMCApp::onCreateWindow(ANativeWindow* window)
198 android_printf("%s: ", __PRETTY_FUNCTION__);
201 android_printf(" => invalid ANativeWindow object");
205 m_windowCreated.Set();
206 if (getWakeLock() && m_wakeLock)
207 m_wakeLock->acquire();
215 void CXBMCApp::onResizeWindow()
217 android_printf("%s: ", __PRETTY_FUNCTION__);
219 m_windowCreated.Reset();
220 // no need to do anything because we are fixed in fullscreen landscape mode
223 void CXBMCApp::onDestroyWindow()
225 android_printf("%s: ", __PRETTY_FUNCTION__);
227 // If we have exited XBMC, it no longer exists.
230 XBMC_DestroyDisplay();
235 m_wakeLock->release();
239 void CXBMCApp::onGainFocus()
241 android_printf("%s: ", __PRETTY_FUNCTION__);
244 void CXBMCApp::onLostFocus()
246 android_printf("%s: ", __PRETTY_FUNCTION__);
249 bool CXBMCApp::getWakeLock()
254 m_wakeLock = new CJNIWakeLock(CJNIPowerManager(getSystemService("power")).newWakeLock("org.xbmc.xbmc"));
264 XBMC::Context context;
266 m_initialVolume = GetSystemVolume();
268 CJNIIntent startIntent = getIntent();
269 android_printf("XBMC Started with action: %s\n",startIntent.getAction().c_str());
271 std::string filenameToPlay = GetFilenameFromIntent(startIntent);
272 if (!filenameToPlay.empty())
275 const char** argv = (const char**) malloc(argc*sizeof(char*));
277 std::string exe_name("XBMC");
278 argv[0] = exe_name.c_str();
279 argv[1] = filenameToPlay.c_str();
281 CAppParamParser appParamParser;
282 appParamParser.Parse((const char **)argv, argc);
288 android_printf(" => running XBMC_Run...");
291 status = XBMC_Run(true);
292 android_printf(" => XBMC_Run finished with %d", status);
296 android_printf("ERROR: Exception caught on main loop. Exiting");
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);
306 void CXBMCApp::XBMC_Pause(bool pause)
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);
314 void CXBMCApp::XBMC_Stop()
316 CApplicationMessenger::Get().Quit();
319 bool CXBMCApp::XBMC_SetupDisplay()
321 android_printf("XBMC_SetupDisplay()");
322 return CApplicationMessenger::Get().SetupDisplay();
325 bool CXBMCApp::XBMC_DestroyDisplay()
327 android_printf("XBMC_DestroyDisplay()");
328 return CApplicationMessenger::Get().DestroyDisplay();
331 int CXBMCApp::SetBuffersGeometry(int width, int height, int format)
333 return ANativeWindow_setBuffersGeometry(m_window, width, height, format);
336 int CXBMCApp::android_printf(const char *format, ...)
338 // For use before CLog is setup by XBMC_Run()
340 va_start(args, format);
341 int result = __android_log_vprint(ANDROID_LOG_VERBOSE, "XBMC", format, args);
346 int CXBMCApp::GetDPI()
348 if (m_activity == NULL || m_activity->assetManager == NULL)
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);
361 std::vector<androidPackage> CXBMCApp::GetApplications()
363 CSingleLock lock(m_applicationsMutex);
364 if (m_applications.empty())
366 CJNIList<CJNIApplicationInfo> packageList = GetPackageManager().getInstalledApplications(CJNIPackageManager::GET_ACTIVITIES);
367 int numPackages = packageList.size();
368 for (int i = 0; i < numPackages; i++)
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"))
377 m_applications.push_back(newPackage);
381 return m_applications;
384 bool CXBMCApp::GetIconSize(const string &packageName, int *width, int *height)
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);
392 *height = info.height;
396 bool CXBMCApp::GetIcon(const string &packageName, void* buffer, unsigned int bufSize)
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);
405 memcpy(buffer, bitmapBuf, bufSize);
406 AndroidBitmap_unlockPixels(env, bitmap.get_raw());
412 bool CXBMCApp::HasLaunchIntent(const string &package)
414 return GetPackageManager().getLaunchIntentForPackage(package) != NULL;
417 // Note intent, dataType, dataURI all default to ""
418 bool CXBMCApp::StartActivity(const string &package, const string &intent, const string &dataType, const string &dataURI)
420 CJNIIntent newIntent = intent.empty() ?
421 GetPackageManager().getLaunchIntentForPackage(package) :
427 if (!dataURI.empty())
429 CJNIURI jniURI = CJNIURI::parse(dataURI);
434 newIntent.setDataAndType(jniURI, dataType);
437 newIntent.setPackage(package);
438 startActivity(newIntent);
439 if (xbmc_jnienv()->ExceptionOccurred())
441 CLog::Log(LOGERROR, "CXBMCApp::StartActivity - ExceptionOccurred launching %s", package.c_str());
442 xbmc_jnienv()->ExceptionClear();
449 int CXBMCApp::GetBatteryLevel()
451 return m_batteryLevel;
454 bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
457 std::string mountedState;
458 bool mounted = false;
460 if(type == "files" || type.empty())
462 CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
464 path = external.getAbsolutePath();
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
480 CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
482 path = external.getAbsolutePath();
485 mountedState = CJNIEnvironment::getExternalStorageState();
486 mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
487 return mounted && !path.empty();
490 bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage)
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 %";
505 CJNIStatFs fileStat(path);
506 int blockSize = fileStat.getBlockSize();
507 int blockCount = fileStat.getBlockCount();
508 int freeBlocks = fileStat.getFreeBlocks();
510 if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0)
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;
518 std::ostringstream fmt;
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
526 fmt.width(12); fmt << usedPercentage << "%"; // percentage used
532 // Used in Application.cpp to figure out volume steps
533 int CXBMCApp::GetMaxSystemVolume()
535 JNIEnv* env = xbmc_jnienv();
536 static int maxVolume = -1;
539 maxVolume = GetMaxSystemVolume(env);
541 //android_printf("CXBMCApp::GetMaxSystemVolume: %i",maxVolume);
545 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
547 CJNIAudioManager audioManager(getSystemService("audio"));
549 return audioManager.getStreamMaxVolume();
550 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
554 int CXBMCApp::GetSystemVolume()
556 CJNIAudioManager audioManager(getSystemService("audio"));
558 return audioManager.getStreamVolume();
561 android_printf("CXBMCApp::GetSystemVolume: Could not get Audio Manager");
566 void CXBMCApp::SetSystemVolume(int val)
568 CJNIAudioManager audioManager(getSystemService("audio"));
570 audioManager.setStreamVolume(val);
572 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
575 void CXBMCApp::SetSystemVolume(JNIEnv *env, float percent)
577 CJNIAudioManager audioManager(getSystemService("audio"));
578 int maxVolume = (int)(GetMaxSystemVolume() * percent);
580 audioManager.setStreamVolume(maxVolume);
582 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
585 void CXBMCApp::onReceive(CJNIIntent intent)
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);
593 void CXBMCApp::onNewIntent(CJNIIntent intent)
595 std::string action = intent.getAction();
596 if (action == "android.intent.action.VIEW")
598 std::string playFile = GetFilenameFromIntent(intent);
599 CApplicationMessenger::Get().MediaPlay(playFile);
603 void CXBMCApp::SetupEnv()
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);
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);
614 std::string externalDir;
615 CJNIFile androidPath = getExternalFilesDir("");
617 androidPath = getDir("org.xbmc.xbmc", 1);
620 externalDir = androidPath.getAbsolutePath();
622 if (!externalDir.empty())
623 setenv("HOME", externalDir.c_str(), 0);
625 setenv("HOME", getenv("XBMC_TEMP"), 0);
628 std::string CXBMCApp::GetFilenameFromIntent(const CJNIIntent &intent)
633 CJNIURI data = intent.getData();
636 std::string scheme = data.getScheme();
637 StringUtils::ToLower(scheme);
638 if (scheme == "content")
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())
645 int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
646 ret = cursor.getString(columnIndex);
650 else if(scheme == "file")
651 ret = data.getPath();
653 ret = data.toString();
657 const ANativeWindow** CXBMCApp::GetNativeWindow(int timeout)
660 return (const ANativeWindow**)&m_window;
662 m_windowCreated.WaitMSec(timeout);
663 return (const ANativeWindow**)&m_window;