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 = GetPackageManager().getLaunchIntentForPackage(package);
408 if (!dataURI.empty())
409 newIntent.setData(dataURI);
412 newIntent.setAction(intent);
414 startActivity(newIntent);
418 int CXBMCApp::GetBatteryLevel()
420 return m_batteryLevel;
423 bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
426 std::string mountedState;
427 bool mounted = false;
429 if(type == "files" || type.empty())
431 CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
433 path = external.getAbsolutePath();
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
449 CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
451 path = external.getAbsolutePath();
454 mountedState = CJNIEnvironment::getExternalStorageState();
455 mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
456 return mounted && !path.empty();
459 bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage)
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 %";
474 CJNIStatFs fileStat(path);
475 int blockSize = fileStat.getBlockSize();
476 int blockCount = fileStat.getBlockCount();
477 int freeBlocks = fileStat.getFreeBlocks();
479 if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0)
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;
487 std::ostringstream fmt;
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
495 fmt.width(12); fmt << usedPercentage << "%"; // percentage used
501 // Used in Application.cpp to figure out volume steps
502 int CXBMCApp::GetMaxSystemVolume()
504 JNIEnv* env = xbmc_jnienv();
505 static int maxVolume = -1;
508 maxVolume = GetMaxSystemVolume(env);
510 //android_printf("CXBMCApp::GetMaxSystemVolume: %i",maxVolume);
514 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
516 CJNIAudioManager audioManager(getSystemService("audio"));
518 return audioManager.getStreamMaxVolume();
519 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
523 int CXBMCApp::GetSystemVolume()
525 CJNIAudioManager audioManager(getSystemService("audio"));
527 return audioManager.getStreamVolume();
530 android_printf("CXBMCApp::GetSystemVolume: Could not get Audio Manager");
535 void CXBMCApp::SetSystemVolume(int val)
537 CJNIAudioManager audioManager(getSystemService("audio"));
539 audioManager.setStreamVolume(val);
541 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
544 void CXBMCApp::SetSystemVolume(JNIEnv *env, float percent)
546 CJNIAudioManager audioManager(getSystemService("audio"));
547 int maxVolume = (int)(GetMaxSystemVolume() * percent);
549 audioManager.setStreamVolume(maxVolume);
551 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
554 void CXBMCApp::onReceive(CJNIIntent intent)
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);
562 void CXBMCApp::onNewIntent(CJNIIntent intent)
564 std::string action = intent.getAction();
565 if (action == "android.intent.action.VIEW")
567 std::string playFile = GetFilenameFromIntent(intent);
568 CApplicationMessenger::Get().MediaPlay(playFile);
572 void CXBMCApp::SetupEnv()
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);
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);
583 std::string externalDir;
584 CJNIFile androidPath = getExternalFilesDir("");
586 androidPath = getDir("org.xbmc.xbmc", 1);
589 externalDir = androidPath.getAbsolutePath();
591 if (!externalDir.empty())
592 setenv("HOME", externalDir.c_str(), 0);
594 setenv("HOME", getenv("XBMC_TEMP"), 0);
597 std::string CXBMCApp::GetFilenameFromIntent(const CJNIIntent &intent)
602 CJNIURI data = intent.getData();
605 std::string scheme = data.getScheme();
606 StringUtils::ToLower(scheme);
607 if (scheme == "content")
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())
614 int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
615 ret = cursor.getString(columnIndex);
619 else if(scheme == "file")
620 ret = data.getPath();
622 ret = data.toString();
626 const ANativeWindow** CXBMCApp::GetNativeWindow(int timeout)
629 return (const ANativeWindow**)&m_window;
631 m_windowCreated.WaitMSec(timeout);
632 return (const ANativeWindow**)&m_window;