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;
91 CXBMCApp::CXBMCApp(ANativeActivity* nativeActivity)
92 : CJNIContext(nativeActivity)
93 , CJNIBroadcastReceiver("org/xbmc/xbmc/XBMCBroadcastReceiver")
96 m_activity = nativeActivity;
99 if (m_activity == NULL)
101 android_printf("CXBMCApp: invalid ANativeActivity instance");
107 CXBMCApp::~CXBMCApp()
111 void CXBMCApp::onStart()
113 android_printf("%s: ", __PRETTY_FUNCTION__);
116 android_printf("%s: Already running, ignoring request to start", __PRETTY_FUNCTION__);
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);
126 void CXBMCApp::onResume()
128 android_printf("%s: ", __PRETTY_FUNCTION__);
129 CJNIIntentFilter batteryFilter;
130 batteryFilter.addAction("android.intent.action.BATTERY_CHANGED");
131 registerReceiver(*this, batteryFilter);
134 void CXBMCApp::onPause()
136 android_printf("%s: ", __PRETTY_FUNCTION__);
139 SetSystemVolume(m_initialVolume);
141 unregisterReceiver(*this);
144 void CXBMCApp::onStop()
146 android_printf("%s: ", __PRETTY_FUNCTION__);
149 void CXBMCApp::onDestroy()
151 android_printf("%s", __PRETTY_FUNCTION__);
153 // If android is forcing us to stop, ask XBMC to exit then wait until it's
158 pthread_join(m_thread, NULL);
159 android_printf(" => XBMC finished");
162 if (m_wakeLock != NULL && m_activity != NULL)
169 void CXBMCApp::onSaveState(void **data, size_t *size)
171 android_printf("%s: ", __PRETTY_FUNCTION__);
172 // no need to save anything as XBMC is running in its own thread
175 void CXBMCApp::onConfigurationChanged()
177 android_printf("%s: ", __PRETTY_FUNCTION__);
178 // ignore any configuration changes like screen rotation etc
181 void CXBMCApp::onLowMemory()
183 android_printf("%s: ", __PRETTY_FUNCTION__);
184 // can't do much as we don't want to close completely
187 void CXBMCApp::onCreateWindow(ANativeWindow* window)
189 android_printf("%s: ", __PRETTY_FUNCTION__);
192 android_printf(" => invalid ANativeWindow object");
196 m_windowCreated.Set();
197 if (getWakeLock() && m_wakeLock)
198 m_wakeLock->acquire();
206 void CXBMCApp::onResizeWindow()
208 android_printf("%s: ", __PRETTY_FUNCTION__);
210 m_windowCreated.Reset();
211 // no need to do anything because we are fixed in fullscreen landscape mode
214 void CXBMCApp::onDestroyWindow()
216 android_printf("%s: ", __PRETTY_FUNCTION__);
218 // If we have exited XBMC, it no longer exists.
221 XBMC_DestroyDisplay();
226 m_wakeLock->release();
230 void CXBMCApp::onGainFocus()
232 android_printf("%s: ", __PRETTY_FUNCTION__);
235 void CXBMCApp::onLostFocus()
237 android_printf("%s: ", __PRETTY_FUNCTION__);
240 bool CXBMCApp::getWakeLock()
245 m_wakeLock = new CJNIWakeLock(CJNIPowerManager(getSystemService("power")).newWakeLock("org.xbmc.xbmc"));
255 XBMC::Context context;
257 m_initialVolume = GetSystemVolume();
259 CJNIIntent startIntent = getIntent();
260 android_printf("XBMC Started with action: %s\n",startIntent.getAction().c_str());
262 std::string filenameToPlay = GetFilenameFromIntent(startIntent);
263 if (!filenameToPlay.empty())
266 const char** argv = (const char**) malloc(argc*sizeof(char*));
268 std::string exe_name("XBMC");
269 argv[0] = exe_name.c_str();
270 argv[1] = filenameToPlay.c_str();
272 CAppParamParser appParamParser;
273 appParamParser.Parse((const char **)argv, argc);
279 android_printf(" => running XBMC_Run...");
282 status = XBMC_Run(true);
283 android_printf(" => XBMC_Run finished with %d", status);
287 android_printf("ERROR: Exception caught on main loop. Exiting");
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);
297 void CXBMCApp::XBMC_Pause(bool pause)
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);
305 void CXBMCApp::XBMC_Stop()
307 CApplicationMessenger::Get().Quit();
310 bool CXBMCApp::XBMC_SetupDisplay()
312 android_printf("XBMC_SetupDisplay()");
313 return CApplicationMessenger::Get().SetupDisplay();
316 bool CXBMCApp::XBMC_DestroyDisplay()
318 android_printf("XBMC_DestroyDisplay()");
319 return CApplicationMessenger::Get().DestroyDisplay();
322 int CXBMCApp::SetBuffersGeometry(int width, int height, int format)
324 return ANativeWindow_setBuffersGeometry(m_window, width, height, format);
327 int CXBMCApp::android_printf(const char *format, ...)
329 // For use before CLog is setup by XBMC_Run()
331 va_start(args, format);
332 int result = __android_log_vprint(ANDROID_LOG_VERBOSE, "XBMC", format, args);
337 int CXBMCApp::GetDPI()
339 if (m_activity == NULL || m_activity->assetManager == NULL)
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);
352 bool CXBMCApp::ListApplications(vector<androidPackage> *applications)
354 CJNIList<CJNIApplicationInfo> packageList = GetPackageManager().getInstalledApplications(CJNIPackageManager::GET_ACTIVITIES);
355 int numPackages = packageList.size();
356 for (int i = 0; i < numPackages; i++)
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"))
365 applications->push_back(newPackage);
370 bool CXBMCApp::GetIconSize(const string &packageName, int *width, int *height)
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);
378 *height = info.height;
382 bool CXBMCApp::GetIcon(const string &packageName, void* buffer, unsigned int bufSize)
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);
391 memcpy(buffer, bitmapBuf, bufSize);
392 AndroidBitmap_unlockPixels(env, bitmap.get_raw());
398 bool CXBMCApp::HasLaunchIntent(const string &package)
400 return GetPackageManager().getLaunchIntentForPackage(package) != NULL;
403 // Note intent, dataType, dataURI all default to ""
404 bool CXBMCApp::StartActivity(const string &package, const string &intent, const string &dataType, const string &dataURI)
406 CJNIIntent newIntent = intent.empty() ?
407 GetPackageManager().getLaunchIntentForPackage(package) :
413 if (!dataURI.empty())
415 CJNIURI jniURI = CJNIURI::parse(dataURI);
420 newIntent.setDataAndType(jniURI, dataType);
423 newIntent.setPackage(package);
424 startActivity(newIntent);
425 if (xbmc_jnienv()->ExceptionOccurred())
427 CLog::Log(LOGERROR, "CXBMCApp::StartActivity - ExceptionOccurred launching %s", package.c_str());
428 xbmc_jnienv()->ExceptionClear();
435 int CXBMCApp::GetBatteryLevel()
437 return m_batteryLevel;
440 bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
443 std::string mountedState;
444 bool mounted = false;
446 if(type == "files" || type.empty())
448 CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
450 path = external.getAbsolutePath();
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
466 CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
468 path = external.getAbsolutePath();
471 mountedState = CJNIEnvironment::getExternalStorageState();
472 mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
473 return mounted && !path.empty();
476 bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage)
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 %";
491 CJNIStatFs fileStat(path);
492 int blockSize = fileStat.getBlockSize();
493 int blockCount = fileStat.getBlockCount();
494 int freeBlocks = fileStat.getFreeBlocks();
496 if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0)
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;
504 std::ostringstream fmt;
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
512 fmt.width(12); fmt << usedPercentage << "%"; // percentage used
518 // Used in Application.cpp to figure out volume steps
519 int CXBMCApp::GetMaxSystemVolume()
521 JNIEnv* env = xbmc_jnienv();
522 static int maxVolume = -1;
525 maxVolume = GetMaxSystemVolume(env);
527 //android_printf("CXBMCApp::GetMaxSystemVolume: %i",maxVolume);
531 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
533 CJNIAudioManager audioManager(getSystemService("audio"));
535 return audioManager.getStreamMaxVolume();
536 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
540 int CXBMCApp::GetSystemVolume()
542 CJNIAudioManager audioManager(getSystemService("audio"));
544 return audioManager.getStreamVolume();
547 android_printf("CXBMCApp::GetSystemVolume: Could not get Audio Manager");
552 void CXBMCApp::SetSystemVolume(int val)
554 CJNIAudioManager audioManager(getSystemService("audio"));
556 audioManager.setStreamVolume(val);
558 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
561 void CXBMCApp::SetSystemVolume(JNIEnv *env, float percent)
563 CJNIAudioManager audioManager(getSystemService("audio"));
564 int maxVolume = (int)(GetMaxSystemVolume() * percent);
566 audioManager.setStreamVolume(maxVolume);
568 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
571 void CXBMCApp::onReceive(CJNIIntent intent)
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);
579 void CXBMCApp::onNewIntent(CJNIIntent intent)
581 std::string action = intent.getAction();
582 if (action == "android.intent.action.VIEW")
584 std::string playFile = GetFilenameFromIntent(intent);
585 CApplicationMessenger::Get().MediaPlay(playFile);
589 void CXBMCApp::SetupEnv()
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);
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);
600 std::string externalDir;
601 CJNIFile androidPath = getExternalFilesDir("");
603 androidPath = getDir("org.xbmc.xbmc", 1);
606 externalDir = androidPath.getAbsolutePath();
608 if (!externalDir.empty())
609 setenv("HOME", externalDir.c_str(), 0);
611 setenv("HOME", getenv("XBMC_TEMP"), 0);
614 std::string CXBMCApp::GetFilenameFromIntent(const CJNIIntent &intent)
619 CJNIURI data = intent.getData();
622 std::string scheme = data.getScheme();
623 StringUtils::ToLower(scheme);
624 if (scheme == "content")
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())
631 int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
632 ret = cursor.getString(columnIndex);
636 else if(scheme == "file")
637 ret = data.getPath();
639 ret = data.toString();
643 const ANativeWindow** CXBMCApp::GetNativeWindow(int timeout)
646 return (const ANativeWindow**)&m_window;
648 m_windowCreated.WaitMSec(timeout);
649 return (const ANativeWindow**)&m_window;