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 <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"
74 #define GIGABYTES 1073741824
78 template<class T, void(T::*fn)()>
79 void* thread_run(void* obj)
81 (static_cast<T*>(obj)->*fn)();
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;
90 CXBMCApp::CXBMCApp(ANativeActivity* nativeActivity)
91 : CJNIContext(nativeActivity)
92 , CJNIBroadcastReceiver("org/xbmc/xbmc/XBMCBroadcastReceiver")
95 m_activity = nativeActivity;
98 if (m_activity == NULL)
100 android_printf("CXBMCApp: invalid ANativeActivity instance");
106 CXBMCApp::~CXBMCApp()
110 void CXBMCApp::onStart()
112 android_printf("%s: ", __PRETTY_FUNCTION__);
115 android_printf("%s: Already running, ignoring request to start", __PRETTY_FUNCTION__);
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);
125 void CXBMCApp::onResume()
127 android_printf("%s: ", __PRETTY_FUNCTION__);
128 CJNIIntentFilter batteryFilter;
129 batteryFilter.addAction("android.intent.action.BATTERY_CHANGED");
130 registerReceiver(*this, batteryFilter);
133 void CXBMCApp::onPause()
135 android_printf("%s: ", __PRETTY_FUNCTION__);
138 SetSystemVolume(m_initialVolume);
140 unregisterReceiver(*this);
143 void CXBMCApp::onStop()
145 android_printf("%s: ", __PRETTY_FUNCTION__);
148 void CXBMCApp::onDestroy()
150 android_printf("%s", __PRETTY_FUNCTION__);
152 // If android is forcing us to stop, ask XBMC to exit then wait until it's
157 pthread_join(m_thread, NULL);
158 android_printf(" => XBMC finished");
161 if (m_wakeLock != NULL && m_activity != NULL)
168 void CXBMCApp::onSaveState(void **data, size_t *size)
170 android_printf("%s: ", __PRETTY_FUNCTION__);
171 // no need to save anything as XBMC is running in its own thread
174 void CXBMCApp::onConfigurationChanged()
176 android_printf("%s: ", __PRETTY_FUNCTION__);
177 // ignore any configuration changes like screen rotation etc
180 void CXBMCApp::onLowMemory()
182 android_printf("%s: ", __PRETTY_FUNCTION__);
183 // can't do much as we don't want to close completely
186 void CXBMCApp::onCreateWindow(ANativeWindow* window)
188 android_printf("%s: ", __PRETTY_FUNCTION__);
191 android_printf(" => invalid ANativeWindow object");
195 m_windowCreated.Set();
196 if (getWakeLock() && m_wakeLock)
197 m_wakeLock->acquire();
205 void CXBMCApp::onResizeWindow()
207 android_printf("%s: ", __PRETTY_FUNCTION__);
209 m_windowCreated.Reset();
210 // no need to do anything because we are fixed in fullscreen landscape mode
213 void CXBMCApp::onDestroyWindow()
215 android_printf("%s: ", __PRETTY_FUNCTION__);
217 // If we have exited XBMC, it no longer exists.
220 XBMC_DestroyDisplay();
225 m_wakeLock->release();
229 void CXBMCApp::onGainFocus()
231 android_printf("%s: ", __PRETTY_FUNCTION__);
234 void CXBMCApp::onLostFocus()
236 android_printf("%s: ", __PRETTY_FUNCTION__);
239 bool CXBMCApp::getWakeLock()
244 m_wakeLock = new CJNIWakeLock(CJNIPowerManager(getSystemService("power")).newWakeLock("org.xbmc.xbmc"));
255 m_initialVolume = GetSystemVolume();
257 CJNIIntent startIntent = getIntent();
258 android_printf("XBMC Started with action: %s\n",startIntent.getAction().c_str());
260 std::string filenameToPlay = GetFilenameFromIntent(startIntent);
261 if (!filenameToPlay.empty())
264 const char** argv = (const char**) malloc(argc*sizeof(char*));
266 std::string exe_name("XBMC");
267 argv[0] = exe_name.c_str();
268 argv[1] = filenameToPlay.c_str();
270 CAppParamParser appParamParser;
271 appParamParser.Parse((const char **)argv, argc);
277 android_printf(" => running XBMC_Run...");
280 status = XBMC_Run(true);
281 android_printf(" => XBMC_Run finished with %d", status);
285 android_printf("ERROR: Exception caught on main loop. Exiting");
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);
295 void CXBMCApp::XBMC_Pause(bool pause)
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);
303 void CXBMCApp::XBMC_Stop()
305 CApplicationMessenger::Get().Quit();
308 bool CXBMCApp::XBMC_SetupDisplay()
310 android_printf("XBMC_SetupDisplay()");
311 return CApplicationMessenger::Get().SetupDisplay();
314 bool CXBMCApp::XBMC_DestroyDisplay()
316 android_printf("XBMC_DestroyDisplay()");
317 return CApplicationMessenger::Get().DestroyDisplay();
320 int CXBMCApp::SetBuffersGeometry(int width, int height, int format)
322 return ANativeWindow_setBuffersGeometry(m_window, width, height, format);
325 int CXBMCApp::android_printf(const char *format, ...)
327 // For use before CLog is setup by XBMC_Run()
329 va_start(args, format);
330 int result = __android_log_vprint(ANDROID_LOG_VERBOSE, "XBMC", format, args);
335 int CXBMCApp::GetDPI()
337 if (m_activity == NULL || m_activity->assetManager == NULL)
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);
350 bool CXBMCApp::ListApplications(vector<androidPackage> *applications)
352 CJNIList<CJNIApplicationInfo> packageList = GetPackageManager().getInstalledApplications(CJNIPackageManager::GET_ACTIVITIES);
353 int numPackages = packageList.size();
354 for (int i = 0; i < numPackages; i++)
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"))
363 applications->push_back(newPackage);
368 bool CXBMCApp::GetIconSize(const string &packageName, int *width, int *height)
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);
376 *height = info.height;
380 bool CXBMCApp::GetIcon(const string &packageName, void* buffer, unsigned int bufSize)
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);
389 memcpy(buffer, bitmapBuf, bufSize);
390 AndroidBitmap_unlockPixels(env, bitmap.get_raw());
396 bool CXBMCApp::HasLaunchIntent(const string &package)
398 return GetPackageManager().getLaunchIntentForPackage(package) != NULL;
401 // Note intent, dataType, dataURI all default to ""
402 bool CXBMCApp::StartActivity(const string &package, const string &intent, const string &dataType, const string &dataURI)
404 CJNIIntent newIntent = intent.empty() ?
405 GetPackageManager().getLaunchIntentForPackage(package) :
411 if (!dataURI.empty())
413 CJNIURI jniURI = CJNIURI::parse(dataURI);
418 newIntent.setDataAndType(jniURI, dataType);
421 newIntent.setPackage(package);
422 startActivity(newIntent);
423 if (xbmc_jnienv()->ExceptionOccurred())
425 CLog::Log(LOGERROR, "CXBMCApp::StartActivity - ExceptionOccurred launching %s", package.c_str());
426 xbmc_jnienv()->ExceptionClear();
433 int CXBMCApp::GetBatteryLevel()
435 return m_batteryLevel;
438 bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
441 std::string mountedState;
442 bool mounted = false;
444 if(type == "files" || type.empty())
446 CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
448 path = external.getAbsolutePath();
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
464 CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
466 path = external.getAbsolutePath();
469 mountedState = CJNIEnvironment::getExternalStorageState();
470 mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
471 return mounted && !path.empty();
474 bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage)
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 %";
489 CJNIStatFs fileStat(path);
490 int blockSize = fileStat.getBlockSize();
491 int blockCount = fileStat.getBlockCount();
492 int freeBlocks = fileStat.getFreeBlocks();
494 if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0)
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;
502 std::ostringstream fmt;
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
510 fmt.width(12); fmt << usedPercentage << "%"; // percentage used
516 // Used in Application.cpp to figure out volume steps
517 int CXBMCApp::GetMaxSystemVolume()
519 JNIEnv* env = xbmc_jnienv();
520 static int maxVolume = -1;
523 maxVolume = GetMaxSystemVolume(env);
525 //android_printf("CXBMCApp::GetMaxSystemVolume: %i",maxVolume);
529 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
531 CJNIAudioManager audioManager(getSystemService("audio"));
533 return audioManager.getStreamMaxVolume();
534 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
538 int CXBMCApp::GetSystemVolume()
540 CJNIAudioManager audioManager(getSystemService("audio"));
542 return audioManager.getStreamVolume();
545 android_printf("CXBMCApp::GetSystemVolume: Could not get Audio Manager");
550 void CXBMCApp::SetSystemVolume(int val)
552 CJNIAudioManager audioManager(getSystemService("audio"));
554 audioManager.setStreamVolume(val);
556 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
559 void CXBMCApp::SetSystemVolume(JNIEnv *env, float percent)
561 CJNIAudioManager audioManager(getSystemService("audio"));
562 int maxVolume = (int)(GetMaxSystemVolume() * percent);
564 audioManager.setStreamVolume(maxVolume);
566 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
569 void CXBMCApp::onReceive(CJNIIntent intent)
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);
577 void CXBMCApp::onNewIntent(CJNIIntent intent)
579 std::string action = intent.getAction();
580 if (action == "android.intent.action.VIEW")
582 std::string playFile = GetFilenameFromIntent(intent);
583 CApplicationMessenger::Get().MediaPlay(playFile);
587 void CXBMCApp::SetupEnv()
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);
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);
598 std::string externalDir;
599 CJNIFile androidPath = getExternalFilesDir("");
601 androidPath = getDir("org.xbmc.xbmc", 1);
604 externalDir = androidPath.getAbsolutePath();
606 if (!externalDir.empty())
607 setenv("HOME", externalDir.c_str(), 0);
609 setenv("HOME", getenv("XBMC_TEMP"), 0);
612 std::string CXBMCApp::GetFilenameFromIntent(const CJNIIntent &intent)
617 CJNIURI data = intent.getData();
620 std::string scheme = data.getScheme();
621 StringUtils::ToLower(scheme);
622 if (scheme == "content")
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())
629 int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
630 ret = cursor.getString(columnIndex);
634 else if(scheme == "file")
635 ret = data.getPath();
637 ret = data.toString();
641 const ANativeWindow** CXBMCApp::GetNativeWindow(int timeout)
644 return (const ANativeWindow**)&m_window;
646 m_windowCreated.WaitMSec(timeout);
647 return (const ANativeWindow**)&m_window;