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;
89 CXBMCApp::CXBMCApp(ANativeActivity* nativeActivity)
90 : CJNIContext(nativeActivity)
91 , CJNIBroadcastReceiver("org/xbmc/xbmc/XBMCBroadcastReceiver")
94 m_activity = nativeActivity;
97 if (m_activity == NULL)
99 android_printf("CXBMCApp: invalid ANativeActivity instance");
105 CXBMCApp::~CXBMCApp()
109 void CXBMCApp::onStart()
111 android_printf("%s: ", __PRETTY_FUNCTION__);
114 android_printf("%s: Already running, ignoring request to start", __PRETTY_FUNCTION__);
118 pthread_attr_init(&attr);
119 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
120 pthread_create(&m_thread, &attr, thread_run<CXBMCApp, &CXBMCApp::run>, this);
121 pthread_attr_destroy(&attr);
124 void CXBMCApp::onResume()
126 android_printf("%s: ", __PRETTY_FUNCTION__);
127 CJNIIntentFilter batteryFilter;
128 batteryFilter.addAction("android.intent.action.BATTERY_CHANGED");
129 registerReceiver(*this, batteryFilter);
132 void CXBMCApp::onPause()
134 android_printf("%s: ", __PRETTY_FUNCTION__);
135 unregisterReceiver(*this);
138 void CXBMCApp::onStop()
140 android_printf("%s: ", __PRETTY_FUNCTION__);
143 void CXBMCApp::onDestroy()
145 android_printf("%s", __PRETTY_FUNCTION__);
147 // If android is forcing us to stop, ask XBMC to exit then wait until it's
152 pthread_join(m_thread, NULL);
153 android_printf(" => XBMC finished");
156 if (m_wakeLock != NULL && m_activity != NULL)
163 void CXBMCApp::onSaveState(void **data, size_t *size)
165 android_printf("%s: ", __PRETTY_FUNCTION__);
166 // no need to save anything as XBMC is running in its own thread
169 void CXBMCApp::onConfigurationChanged()
171 android_printf("%s: ", __PRETTY_FUNCTION__);
172 // ignore any configuration changes like screen rotation etc
175 void CXBMCApp::onLowMemory()
177 android_printf("%s: ", __PRETTY_FUNCTION__);
178 // can't do much as we don't want to close completely
181 void CXBMCApp::onCreateWindow(ANativeWindow* window)
183 android_printf("%s: ", __PRETTY_FUNCTION__);
186 android_printf(" => invalid ANativeWindow object");
190 m_windowCreated.Set();
191 if (getWakeLock() && m_wakeLock)
192 m_wakeLock->acquire();
200 void CXBMCApp::onResizeWindow()
202 android_printf("%s: ", __PRETTY_FUNCTION__);
204 m_windowCreated.Reset();
205 // no need to do anything because we are fixed in fullscreen landscape mode
208 void CXBMCApp::onDestroyWindow()
210 android_printf("%s: ", __PRETTY_FUNCTION__);
212 // If we have exited XBMC, it no longer exists.
215 XBMC_DestroyDisplay();
220 m_wakeLock->release();
224 void CXBMCApp::onGainFocus()
226 android_printf("%s: ", __PRETTY_FUNCTION__);
229 void CXBMCApp::onLostFocus()
231 android_printf("%s: ", __PRETTY_FUNCTION__);
234 bool CXBMCApp::getWakeLock()
239 m_wakeLock = new CJNIWakeLock(CJNIPowerManager(getSystemService("power")).newWakeLock("org.xbmc.xbmc"));
250 CJNIIntent startIntent = getIntent();
251 android_printf("XBMC Started with action: %s\n",startIntent.getAction().c_str());
253 std::string filenameToPlay = GetFilenameFromIntent(startIntent);
254 if (!filenameToPlay.empty())
257 const char** argv = (const char**) malloc(argc*sizeof(char*));
259 std::string exe_name("XBMC");
260 argv[0] = exe_name.c_str();
261 argv[1] = filenameToPlay.c_str();
263 CAppParamParser appParamParser;
264 appParamParser.Parse((const char **)argv, argc);
270 android_printf(" => running XBMC_Run...");
273 status = XBMC_Run(true);
274 android_printf(" => XBMC_Run finished with %d", status);
278 android_printf("ERROR: Exception caught on main loop. Exiting");
281 // If we are have not been force by Android to exit, notify its finish routine.
282 // This will cause android to run through its teardown events, it calls:
283 // onPause(), onLostFocus(), onDestroyWindow(), onStop(), onDestroy().
284 ANativeActivity_finish(m_activity);
288 void CXBMCApp::XBMC_Pause(bool pause)
290 android_printf("XBMC_Pause(%s)", pause ? "true" : "false");
291 // Only send the PAUSE action if we are pausing XBMC and something is currently playing
292 if (pause && g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused())
293 CApplicationMessenger::Get().SendAction(CAction(ACTION_PAUSE), WINDOW_INVALID, true);
296 void CXBMCApp::XBMC_Stop()
298 CApplicationMessenger::Get().Quit();
301 bool CXBMCApp::XBMC_SetupDisplay()
303 android_printf("XBMC_SetupDisplay()");
304 return CApplicationMessenger::Get().SetupDisplay();
307 bool CXBMCApp::XBMC_DestroyDisplay()
309 android_printf("XBMC_DestroyDisplay()");
310 return CApplicationMessenger::Get().DestroyDisplay();
313 int CXBMCApp::SetBuffersGeometry(int width, int height, int format)
315 return ANativeWindow_setBuffersGeometry(m_window, width, height, format);
318 int CXBMCApp::android_printf(const char *format, ...)
320 // For use before CLog is setup by XBMC_Run()
322 va_start(args, format);
323 int result = __android_log_vprint(ANDROID_LOG_VERBOSE, "XBMC", format, args);
328 int CXBMCApp::GetDPI()
330 if (m_activity == NULL || m_activity->assetManager == NULL)
333 // grab DPI from the current configuration - this is approximate
334 // but should be close enough for what we need
335 AConfiguration *config = AConfiguration_new();
336 AConfiguration_fromAssetManager(config, m_activity->assetManager);
337 int dpi = AConfiguration_getDensity(config);
338 AConfiguration_delete(config);
343 bool CXBMCApp::ListApplications(vector<androidPackage> *applications)
345 CJNIList<CJNIApplicationInfo> packageList = GetPackageManager().getInstalledApplications(CJNIPackageManager::GET_ACTIVITIES);
346 int numPackages = packageList.size();
347 for (int i = 0; i < numPackages; i++)
349 androidPackage newPackage;
350 newPackage.packageName = packageList.get(i).packageName;
351 newPackage.packageLabel = GetPackageManager().getApplicationLabel(packageList.get(i)).toString();
352 CJNIIntent intent = GetPackageManager().getLaunchIntentForPackage(newPackage.packageName);
353 if (!intent || !intent.hasCategory("android.intent.category.LAUNCHER"))
356 applications->push_back(newPackage);
361 bool CXBMCApp::GetIconSize(const string &packageName, int *width, int *height)
363 JNIEnv* env = xbmc_jnienv();
364 AndroidBitmapInfo info;
365 CJNIBitmapDrawable drawable = (CJNIBitmapDrawable)GetPackageManager().getApplicationIcon(packageName);
366 CJNIBitmap icon(drawable.getBitmap());
367 AndroidBitmap_getInfo(env, icon.get_raw(), &info);
369 *height = info.height;
373 bool CXBMCApp::GetIcon(const string &packageName, void* buffer, unsigned int bufSize)
375 void *bitmapBuf = NULL;
376 JNIEnv* env = xbmc_jnienv();
377 CJNIBitmapDrawable drawable = (CJNIBitmapDrawable)GetPackageManager().getApplicationIcon(packageName);
378 CJNIBitmap bitmap(drawable.getBitmap());
379 AndroidBitmap_lockPixels(env, bitmap.get_raw(), &bitmapBuf);
382 memcpy(buffer, bitmapBuf, bufSize);
383 AndroidBitmap_unlockPixels(env, bitmap.get_raw());
389 bool CXBMCApp::HasLaunchIntent(const string &package)
391 return GetPackageManager().getLaunchIntentForPackage(package) != NULL;
394 // Note intent, dataType, dataURI all default to ""
395 bool CXBMCApp::StartActivity(const string &package, const string &intent, const string &dataType, const string &dataURI)
397 CJNIIntent newIntent = GetPackageManager().getLaunchIntentForPackage(package);
401 if (!dataURI.empty())
402 newIntent.setData(dataURI);
405 newIntent.setAction(intent);
407 startActivity(newIntent);
411 int CXBMCApp::GetBatteryLevel()
413 return m_batteryLevel;
416 bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
419 std::string mountedState;
420 bool mounted = false;
422 if(type == "files" || type.empty())
424 CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
426 path = external.getAbsolutePath();
431 sType = "Music"; // Environment.DIRECTORY_MUSIC
432 else if (type == "videos")
433 sType = "Movies"; // Environment.DIRECTORY_MOVIES
434 else if (type == "pictures")
435 sType = "Pictures"; // Environment.DIRECTORY_PICTURES
436 else if (type == "photos")
437 sType = "DCIM"; // Environment.DIRECTORY_DCIM
438 else if (type == "downloads")
439 sType = "Download"; // Environment.DIRECTORY_DOWNLOADS
442 CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
444 path = external.getAbsolutePath();
447 mountedState = CJNIEnvironment::getExternalStorageState();
448 mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
449 return mounted && !path.empty();
452 bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage)
456 std::ostringstream fmt;
457 fmt.width(24); fmt << std::left << "Filesystem";
458 fmt.width(12); fmt << std::right << "Size";
459 fmt.width(12); fmt << "Used";
460 fmt.width(12); fmt << "Avail";
461 fmt.width(12); fmt << "Use %";
467 CJNIStatFs fileStat(path);
468 int blockSize = fileStat.getBlockSize();
469 int blockCount = fileStat.getBlockCount();
470 int freeBlocks = fileStat.getFreeBlocks();
472 if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0)
475 float totalSize = (float)blockSize * blockCount / GIGABYTES;
476 float freeSize = (float)blockSize * freeBlocks / GIGABYTES;
477 float usedSize = totalSize - freeSize;
478 float usedPercentage = usedSize / totalSize * 100;
480 std::ostringstream fmt;
483 fmt.width(24); fmt << std::left << path;
484 fmt.width(12); fmt << std::right << totalSize << "G"; // size in GB
485 fmt.width(12); fmt << usedSize << "G"; // used in GB
486 fmt.width(12); fmt << freeSize << "G"; // free
488 fmt.width(12); fmt << usedPercentage << "%"; // percentage used
494 // Used in Application.cpp to figure out volume steps
495 int CXBMCApp::GetMaxSystemVolume()
497 JNIEnv* env = xbmc_jnienv();
498 static int maxVolume = -1;
501 maxVolume = GetMaxSystemVolume(env);
503 android_printf("CXBMCApp::GetMaxSystemVolume: %i",maxVolume);
507 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
509 CJNIAudioManager audioManager(getSystemService("audio"));
511 return audioManager.getStreamMaxVolume();
512 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
516 void CXBMCApp::SetSystemVolume(JNIEnv *env, float percent)
518 CJNIAudioManager audioManager(getSystemService("audio"));
519 int maxVolume = (int)(GetMaxSystemVolume() * percent);
521 audioManager.setStreamVolume(maxVolume);
523 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
526 void CXBMCApp::onReceive(CJNIIntent intent)
528 std::string action = intent.getAction();
529 android_printf("CXBMCApp::onReceive Got intent. Action: %s", action.c_str());
530 if (action == "android.intent.action.BATTERY_CHANGED")
531 m_batteryLevel = intent.getIntExtra("level",-1);
534 void CXBMCApp::onNewIntent(CJNIIntent intent)
536 std::string action = intent.getAction();
537 if (action == "android.intent.action.VIEW")
539 std::string playFile = GetFilenameFromIntent(intent);
540 CApplicationMessenger::Get().MediaPlay(playFile);
544 void CXBMCApp::SetupEnv()
546 setenv("XBMC_ANDROID_SYSTEM_LIBS", CJNISystem::getProperty("java.library.path").c_str(), 0);
547 setenv("XBMC_ANDROID_DATA", getApplicationInfo().dataDir.c_str(), 0);
548 setenv("XBMC_ANDROID_LIBS", getApplicationInfo().nativeLibraryDir.c_str(), 0);
549 setenv("XBMC_ANDROID_APK", getPackageResourcePath().c_str(), 0);
551 std::string cacheDir = getCacheDir().getAbsolutePath();
552 setenv("XBMC_BIN_HOME", (cacheDir + "/apk/assets").c_str(), 0);
553 setenv("XBMC_HOME", (cacheDir + "/apk/assets").c_str(), 0);
555 std::string externalDir;
556 CJNIFile androidPath = getExternalFilesDir("");
558 androidPath = getDir("org.xbmc.xbmc", 1);
561 externalDir = androidPath.getAbsolutePath();
563 if (!externalDir.empty())
564 setenv("HOME", externalDir.c_str(), 0);
566 setenv("HOME", getenv("XBMC_TEMP"), 0);
569 std::string CXBMCApp::GetFilenameFromIntent(const CJNIIntent &intent)
574 CJNIURI data = intent.getData();
577 std::string scheme = data.getScheme();
578 StringUtils::ToLower(scheme);
579 if (scheme == "content")
581 std::vector<std::string> filePathColumn;
582 filePathColumn.push_back(CJNIMediaStoreMediaColumns::DATA);
583 CJNICursor cursor = getContentResolver().query(data, filePathColumn, std::string(), std::vector<std::string>(), std::string());
584 if(cursor.moveToFirst())
586 int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
587 ret = cursor.getString(columnIndex);
591 else if(scheme == "file")
592 ret = data.getPath();
594 ret = data.toString();
598 const ANativeWindow** CXBMCApp::GetNativeWindow(int timeout)
601 return (const ANativeWindow**)&m_window;
603 m_windowCreated.WaitMSec(timeout);
604 return (const ANativeWindow**)&m_window;