/* * Copyright (C) 2012 Simon Robinson * * This file is part of Com-Me. * * Com-Me is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * Com-Me is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General * Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Com-Me. * If not, see <http://www.gnu.org/licenses/>. */ package ac.robinson.mediatablet; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; import ac.robinson.mediatablet.provider.PersonManager; import ac.robinson.mediautilities.MediaUtilities; import ac.robinson.service.ImportingService; import ac.robinson.util.DebugUtilities; import ac.robinson.util.IOUtilities; import android.app.Application; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.os.StrictMode; import android.preference.PreferenceManager; import android.util.Log; public class MediaTabletApplication extends Application { // for watching SD card state private BroadcastReceiver mExternalStorageReceiver; // for communicating with the importing service private Messenger mImportingService = null; private boolean mImportingServiceIsBound; private static WeakReference<MediaTabletActivity> mCurrentActivity = null; private static List<MessageContainer> mSavedMessages = Collections .synchronizedList(new ArrayList<MessageContainer>()); // because messages are reused we need to save their contents instead private static class MessageContainer { public int what; public String data; } // for clients to communicate with the ImportingService private final Messenger mImportingServiceMessenger = new Messenger(new ImportingServiceMessageHandler()); @Override public void onCreate() { if (MediaTablet.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build()); } super.onCreate(); try { PersonManager.lockAllPeople(getContentResolver()); } catch (Throwable t) { } initialiseDirectories(); startWatchingExternalStorage(); } private void initialiseDirectories() { // make sure we use the right storage location regardless of whether the application has been moved between // SD card and tablet; we check for missing files in each activity, so no need to do so here // TODO: add a way of moving content to/from internal/external locations (e.g., in prefs, select device/SD) boolean useSDCard = true; final String storageKey = getString(R.string.key_use_external_storage); final String storageDirectoryName = MediaTablet.APPLICATION_NAME + getString(R.string.name_storage_directory); SharedPreferences mediaTabletSettings = getSharedPreferences(MediaTablet.APPLICATION_NAME, Context.MODE_PRIVATE); if (mediaTabletSettings.contains(storageKey)) { // setting has previously been saved useSDCard = mediaTabletSettings.getBoolean(storageKey, true); // defValue is irrelevant; know value exists MediaTablet.DIRECTORY_STORAGE = IOUtilities.getNewStoragePath(this, storageDirectoryName, useSDCard); if (useSDCard && MediaTablet.DIRECTORY_STORAGE != null) { // we needed the SD card but couldn't get it; our files will be missing - null shows warning and exits if (IOUtilities.isInternalPath(MediaTablet.DIRECTORY_STORAGE.getAbsolutePath())) { MediaTablet.DIRECTORY_STORAGE = null; } } } else { // first run - prefer SD card for storage MediaTablet.DIRECTORY_STORAGE = IOUtilities.getNewStoragePath(this, storageDirectoryName, true); if (MediaTablet.DIRECTORY_STORAGE != null) { useSDCard = !IOUtilities.isInternalPath(MediaTablet.DIRECTORY_STORAGE.getAbsolutePath()); SharedPreferences.Editor prefsEditor = mediaTabletSettings.edit(); prefsEditor.putBoolean(storageKey, useSDCard); prefsEditor.apply(); } } // use cache directories for thumbnails and temp (outgoing) files; don't clear MediaTablet.DIRECTORY_THUMBS = IOUtilities.getNewCachePath(this, MediaTablet.APPLICATION_NAME + getString(R.string.name_thumbs_directory), useSDCard, false); // temp directory must be world readable to be able to send files, so always prefer external (checked on export) MediaTablet.DIRECTORY_TEMP = IOUtilities.getNewCachePath(this, MediaTablet.APPLICATION_NAME + getString(R.string.name_temp_directory), true, true); } private void startWatchingExternalStorage() { mExternalStorageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (MediaTablet.DEBUG) { Log.d(DebugUtilities.getLogTag(this), "SD card state changed to: " + intent.getAction()); } initialiseDirectories(); // check storage still present; switch to external temp directory if possible if (mCurrentActivity != null) { MediaTabletActivity currentActivity = mCurrentActivity.get(); if (currentActivity != null) { currentActivity.checkDirectoriesExist(); // validate directories; finish() on error } } } }; IntentFilter filter = new IntentFilter(); // filter.addAction(Intent.ACTION_MEDIA_EJECT); //TODO: deal with this event? filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); filter.addAction(Intent.ACTION_MEDIA_MOUNTED); filter.addAction(Intent.ACTION_MEDIA_NOFS); filter.addAction(Intent.ACTION_MEDIA_REMOVED); filter.addAction(Intent.ACTION_MEDIA_SHARED); filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); filter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE); filter.addDataScheme("file"); // nowhere do the API docs mention this requirement... registerReceiver(mExternalStorageReceiver, filter); } @SuppressWarnings("unused") private void stopWatchingExternalStorage() { // TODO: call this on application exit if possible unregisterReceiver(mExternalStorageReceiver); } public void registerActivityHandle(MediaTabletActivity activity) { if (mCurrentActivity != null) { mCurrentActivity.clear(); mCurrentActivity = null; } mCurrentActivity = new WeakReference<MediaTabletActivity>(activity); for (MessageContainer msg : mSavedMessages) { // must duplicate the data here, or we crash Message clientMessage = Message.obtain(null, msg.what, 0, 0); Bundle messageBundle = new Bundle(); messageBundle.putString(MediaUtilities.KEY_FILE_NAME, msg.data); clientMessage.setData(messageBundle); activity.processIncomingFiles(clientMessage); } mSavedMessages.clear(); } public void removeActivityHandle(MediaTabletActivity activity) { if (mCurrentActivity != null) { if (mCurrentActivity.get().equals(activity)) { mCurrentActivity.clear(); mCurrentActivity = null; } } } private static class ImportingServiceMessageHandler extends Handler { @Override public void handleMessage(final Message msg) { if (mCurrentActivity != null) { MediaTabletActivity currentActivity = mCurrentActivity.get(); if (currentActivity != null) { currentActivity.processIncomingFiles(msg); } } else { MessageContainer clientMessage = new MessageContainer(); clientMessage.what = msg.what; clientMessage.data = msg.peekData().getString(MediaUtilities.KEY_FILE_NAME); mSavedMessages.add(clientMessage); } } } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { mImportingService = new Messenger(service); try { Message msg = Message.obtain(null, MediaUtilities.MSG_REGISTER_CLIENT); msg.replyTo = mImportingServiceMessenger; mImportingService.send(msg); } catch (RemoteException e) { // service has crashed before connecting; will be disconnected, restarted & reconnected automatically } } public void onServiceDisconnected(ComponentName className) { mImportingService = null; // unexpectedly disconnected/crashed } }; public void startWatchingBluetooth() { SharedPreferences mediaTabletSettings = PreferenceManager .getDefaultSharedPreferences(MediaTabletApplication.this); String watchedDirectory = getString(R.string.default_bluetooth_directory); if (!(new File(watchedDirectory).exists())) { watchedDirectory = getString(R.string.default_bluetooth_directory_alternative); } try { String settingsDirectory = mediaTabletSettings.getString(getString(R.string.key_bluetooth_directory), watchedDirectory); watchedDirectory = settingsDirectory; // could check exists, but don't, to ensure setting overrides default } catch (Exception e) { } boolean directoryExists = (new File(watchedDirectory)).exists(); if (!watchedDirectory.equals(MediaTablet.IMPORT_DIRECTORY) || !directoryExists) { stopWatchingBluetooth(); MediaTablet.IMPORT_DIRECTORY = watchedDirectory; } if (!directoryExists) { return; // can't watch if the directory doesn't exist } if (!mImportingServiceIsBound) { final Intent bindIntent = new Intent(MediaTabletApplication.this, ImportingService.class); bindIntent.putExtra(MediaUtilities.KEY_OBSERVER_CLASS, "ac.robinson.mediatablet.importing.BluetoothObserver"); bindIntent.putExtra(MediaUtilities.KEY_OBSERVER_PATH, MediaTablet.IMPORT_DIRECTORY); bindIntent.putExtra(MediaUtilities.KEY_OBSERVER_REQUIRE_BT, true); bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE); mImportingServiceIsBound = true; } } public void stopWatchingBluetooth() { if (mImportingServiceIsBound) { if (mImportingService != null) { try { Message msg = Message.obtain(null, MediaUtilities.MSG_DISCONNECT_CLIENT); msg.replyTo = mImportingServiceMessenger; mImportingService.send(msg); } catch (RemoteException e) { } } unbindService(mConnection); mImportingServiceIsBound = false; } } }