/* * Created by Angel Leon (@gubatron), Alden Torres (aldenml) * Copyright (c) 2011, 2012, FrostWire(TM). All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.bt.download.android.gui; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.*; import com.bt.download.android.core.*; import com.bt.download.android.gui.transfers.Transfers; import org.apache.commons.io.FilenameUtils; import org.xmlpull.v1.XmlPullParser; import android.app.Application; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.res.XmlResourceParser; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Process; import android.provider.BaseColumns; import android.provider.MediaStore.MediaColumns; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.view.WindowManager; import com.bt.download.android.core.player.EphemeralPlaylist; import com.bt.download.android.core.player.PlaylistItem; import com.bt.download.android.core.providers.TableFetcher; import com.bt.download.android.core.providers.TableFetchers; import com.bt.download.android.core.providers.UniversalStore; import com.bt.download.android.core.providers.UniversalStore.Applications; import com.bt.download.android.core.providers.UniversalStore.Applications.ApplicationsColumns; import com.bt.download.android.core.providers.UniversalStore.Sharing; import com.bt.download.android.core.providers.UniversalStore.Sharing.SharingColumns; import com.bt.download.android.gui.util.Apk; import com.frostwire.util.StringUtils; import com.frostwire.localpeer.Finger; import com.frostwire.localpeer.ScreenMetrics; import com.frostwire.util.DirectoryUtils; /** * The Librarian is in charge of: * -> Keeping track of what files we're sharing or not. * -> Indexing the files we're sharing. * -> Searching for files we're sharing. * * @author gubatron * @author aldenml * */ public final class Librarian { private static final String TAG = "FW.Librarian"; private final Application context; private final FileCountCache[] cache; // it is an array for performance reasons private static Librarian instance; public synchronized static void create(Application context) { if (instance != null) { return; } instance = new Librarian(context); } public static Librarian instance() { if (instance == null) { throw new CoreRuntimeException("Librarian not created"); } return instance; } private Librarian(Application context) { this.context = context; this.cache = new FileCountCache[] { new FileCountCache(), new FileCountCache(), new FileCountCache(), new FileCountCache(), new FileCountCache(), new FileCountCache() }; } public List<FileDescriptor> getFiles(byte fileType, int offset, int pageSize, boolean sharedOnly) { return getFiles(offset, pageSize, TableFetchers.getFetcher(fileType), sharedOnly); } public List<FileDescriptor> getFiles(byte fileType, String where, String[] whereArgs) { return getFiles(0, Integer.MAX_VALUE, TableFetchers.getFetcher(fileType), where, whereArgs, false); } /** * Returns the total number of shared files by this peer. note: each of the * number of shared files (by type) is stored on _cacheNumFiles, this * function returns the sum of this cache. * * @return */ public int getNumFiles() { int result = 0; for (byte i = 0; i < 6; i++) { //update numbers if you have to. if (!cache[i].cacheValid(true)) { cache[i].updateShared(getNumFiles(i, true)); } result += cache[i].shared; } return result < 0 ? 0 : result; } /** * * @param fileType * @param onlyShared - If false, forces getting all files, shared or unshared. * @return */ public int getNumFiles(byte fileType, boolean onlyShared) { TableFetcher fetcher = TableFetchers.getFetcher(fileType); if (cache[fileType].cacheValid(onlyShared)) { return cache[fileType].getCount(onlyShared); } Cursor c = null; int result = 0; int numFiles = 0; try { ContentResolver cr = context.getContentResolver(); c = cr.query(fetcher.getContentUri(), new String[] { BaseColumns._ID }, null, null, null); numFiles = c != null ? c.getCount() : 0; } catch (Exception e) { Log.e(TAG, "Failed to get num of files", e); } finally { if (c != null) { c.close(); } } result = onlyShared ? (getSharedFiles(fileType).size()) : numFiles; updateCacheNumFiles(fileType, result, onlyShared); return result; } public FileDescriptor getFileDescriptor(byte fileType, int fileId) { return getFileDescriptor(fileType, fileId, true); } public FileDescriptor getFileDescriptor(byte fileType, int fileId, boolean sharedOnly) { List<FileDescriptor> fds = getFiles(0, 1, TableFetchers.getFetcher(fileType), BaseColumns._ID + "=?", new String[] { String.valueOf(fileId) }, sharedOnly); if (fds.size() > 0) { return fds.get(0); } else { return null; } } public String renameFile(FileDescriptor fd, String newFileName) { try { String filePath = fd.filePath; File oldFile = new File(filePath); String ext = FilenameUtils.getExtension(filePath); File newFile = new File(oldFile.getParentFile(), newFileName + '.' + ext); ContentResolver cr = context.getContentResolver(); ContentValues values = new ContentValues(); values.put(MediaColumns.DATA, newFile.getAbsolutePath()); values.put(MediaColumns.DISPLAY_NAME, FilenameUtils.getBaseName(newFileName)); values.put(MediaColumns.TITLE, FilenameUtils.getBaseName(newFileName)); TableFetcher fetcher = TableFetchers.getFetcher(fd.fileType); cr.update(fetcher.getContentUri(), values, BaseColumns._ID + "=?", new String[] { String.valueOf(fd.id) }); oldFile.renameTo(newFile); return newFile.getAbsolutePath(); } catch (Throwable e) { Log.e(TAG, "Failed to rename file: " + fd, e); } return null; } public void updateSharedStates(byte fileType, List<FileDescriptor> fds) { if (fds == null || fds.size() == 0) { return; } try { ContentResolver cr = context.getContentResolver(); Set<Integer> sharedFiles = getSharedFiles(fds.get(0).fileType); int size = fds.size(); // we know this is a slow process, we can improve it later for (int i = 0; i < size; i++) { FileDescriptor fileDescriptor = fds.get(i); ContentValues contentValues = new ContentValues(); contentValues.put(SharingColumns.SHARED, fileDescriptor.shared ? 1 : 0); // Is this a NEW Shared File? if (!sharedFiles.contains(fileDescriptor.id) && fileDescriptor.shared) { // insert in table as unshared. contentValues.put(SharingColumns.FILE_ID, fileDescriptor.id); contentValues.put(SharingColumns.FILE_TYPE, fileType); cr.insert(Sharing.Media.CONTENT_URI, contentValues); } else { // everything else is an update cr.update(Sharing.Media.CONTENT_URI, contentValues, SharingColumns.FILE_ID + "=? AND " + SharingColumns.FILE_TYPE + "=?", new String[] { String.valueOf(fileDescriptor.id), String.valueOf(fileType) }); } } invalidateCountCache(fileType); } catch (Throwable e) { Log.e(TAG, "Failed to update share states", e); } } public void deleteFiles(byte fileType, Collection<FileDescriptor> fds) { List<Integer> ids = new ArrayList<Integer>(fds.size()); for (FileDescriptor fd : fds) { if (new File(fd.filePath).delete()) { ids.add(fd.id); deleteSharedState(fd.fileType, fd.id); } } try { ContentResolver cr = context.getContentResolver(); TableFetcher fetcher = TableFetchers.getFetcher(fileType); cr.delete(fetcher.getContentUri(), MediaColumns._ID + " IN " + StringUtils.buildSet(ids), null); } catch (Throwable e) { Log.e(TAG, "Failed to delete files from media store", e); } invalidateCountCache(fileType); } public void scan(File file) { scan(file, Transfers.getIgnorableFiles()); } public Finger finger(boolean local) { Finger finger = new Finger(); finger.uuid = ConfigurationManager.instance().getUUIDString(); finger.nickname = ConfigurationManager.instance().getNickname(); finger.frostwireVersion = Constants.FROSTWIRE_VERSION_STRING; finger.totalShared = getNumFiles(); finger.deviceVersion = Build.VERSION.RELEASE; finger.deviceModel = Build.MODEL; finger.deviceProduct = Build.PRODUCT; finger.deviceName = Build.DEVICE; finger.deviceManufacturer = Build.MANUFACTURER; finger.deviceBrand = Build.BRAND; finger.deviceScreen = readScreenMetrics(); finger.numSharedAudioFiles = getNumFiles(Constants.FILE_TYPE_AUDIO, true); finger.numSharedVideoFiles = getNumFiles(Constants.FILE_TYPE_VIDEOS, true); finger.numSharedPictureFiles = getNumFiles(Constants.FILE_TYPE_PICTURES, true); finger.numSharedDocumentFiles = getNumFiles(Constants.FILE_TYPE_DOCUMENTS, true); finger.numSharedApplicationFiles = getNumFiles(Constants.FILE_TYPE_APPLICATIONS, true); finger.numSharedRingtoneFiles = getNumFiles(Constants.FILE_TYPE_RINGTONES, true); if (local) { finger.numTotalAudioFiles = getNumFiles(Constants.FILE_TYPE_AUDIO, false); finger.numTotalVideoFiles = getNumFiles(Constants.FILE_TYPE_VIDEOS, false); finger.numTotalPictureFiles = getNumFiles(Constants.FILE_TYPE_PICTURES, false); finger.numTotalDocumentFiles = getNumFiles(Constants.FILE_TYPE_DOCUMENTS, false); finger.numTotalApplicationFiles = getNumFiles(Constants.FILE_TYPE_APPLICATIONS, false); finger.numTotalRingtoneFiles = getNumFiles(Constants.FILE_TYPE_RINGTONES, false); } else { finger.numTotalAudioFiles = finger.numSharedAudioFiles; finger.numTotalVideoFiles = finger.numSharedVideoFiles; finger.numTotalPictureFiles = finger.numSharedPictureFiles; finger.numTotalDocumentFiles = finger.numSharedDocumentFiles; finger.numTotalApplicationFiles = finger.numSharedApplicationFiles; finger.numTotalRingtoneFiles = finger.numSharedRingtoneFiles; } return finger; } public void syncApplicationsProvider() { if (!isExternalStorageMounted()) { return; } Thread t = new Thread(new Runnable() { public void run() { syncApplicationsProviderSupport(); } }); t.setName("syncApplicationsProvider"); t.setDaemon(true); t.start(); } public void syncMediaStore() { if (!isExternalStorageMounted()) { return; } Thread t = new Thread(new Runnable() { public void run() { syncMediaStoreSupport(); } }); t.setName("syncMediaStore"); t.setDaemon(true); t.start(); } public boolean isExternalStorageMounted() { return com.bt.download.android.util.SystemUtils.isPrimaryExternalStorageMounted(); } public EphemeralPlaylist createEphemeralPlaylist(FileDescriptor fd) { List<FileDescriptor> fds = Librarian.instance().getFiles(Constants.FILE_TYPE_AUDIO, FilenameUtils.getPath(fd.filePath), false); if (fds.size() == 0) { // just in case Log.w(TAG, "Logic error creating ephemeral playlist"); fds.add(fd); } EphemeralPlaylist playlist = new EphemeralPlaylist(fds); playlist.setNextItem(new PlaylistItem(fd)); return playlist; } public void invalidateCountCache() { for (FileCountCache c : cache) { if (c != null) { c.lastTimeCachedShared = 0; c.lastTimeCachedOnDisk = 0; } } broadcastRefreshFinger(); } /** * @param fileType */ void invalidateCountCache(byte fileType) { cache[fileType].lastTimeCachedShared = 0; cache[fileType].lastTimeCachedOnDisk = 0; broadcastRefreshFinger(); } private void broadcastRefreshFinger() { context.sendBroadcast(new Intent(Constants.ACTION_REFRESH_FINGER)); PeerManager.instance().updateLocalPeer(); } private void syncApplicationsProviderSupport() { try { List<FileDescriptor> fds = Librarian.instance().getFiles(Constants.FILE_TYPE_APPLICATIONS, 0, Integer.MAX_VALUE, false); int packagesSize = fds.size(); String[] packages = new String[packagesSize]; for (int i = 0; i < packagesSize; i++) { packages[i] = fds.get(i).album; } Arrays.sort(packages); List<ApplicationInfo> applications = context.getPackageManager().getInstalledApplications(0); int size = applications.size(); ArrayList<String> newPackagesList = new ArrayList<String>(size); for (int i = 0; i < size; i++) { ApplicationInfo appInfo = applications.get(i); try { if (appInfo == null) { continue; } newPackagesList.add(appInfo.packageName); File f = new File(appInfo.sourceDir); if (!f.canRead()) { continue; } int index = Arrays.binarySearch(packages, appInfo.packageName); if (index >= 0) { continue; } String data = appInfo.sourceDir; String title = appInfo.packageName; String packageName = appInfo.packageName; String version = ""; Apk apk = new Apk(context, appInfo.sourceDir); String[] result = parseApk(apk); if (result != null) { if (result[1] == null) { continue; } title = result[1]; version = result[0]; } ContentValues cv = new ContentValues(); cv.put(ApplicationsColumns.DATA, data); cv.put(ApplicationsColumns.SIZE, f.length()); cv.put(ApplicationsColumns.TITLE, title); cv.put(ApplicationsColumns.MIME_TYPE, Constants.MIME_TYPE_ANDROID_PACKAGE_ARCHIVE); cv.put(ApplicationsColumns.VERSION, version); cv.put(ApplicationsColumns.PACKAGE_NAME, packageName); ContentResolver cr = context.getContentResolver(); Uri uri = cr.insert(Applications.Media.CONTENT_URI, cv); if (appInfo.icon != 0) { try { InputStream is = null; OutputStream os = null; try { is = apk.openRawResource(appInfo.icon); os = cr.openOutputStream(uri); byte[] buff = new byte[4 * 1024]; int n = 0; while ((n = is.read(buff, 0, buff.length)) != -1) { os.write(buff, 0, n); } } finally { if (os != null) { os.close(); } if (is != null) { is.close(); } } } catch (Throwable e) { Log.e(TAG, "Can't retrieve icon image for application " + appInfo.packageName); } } } catch (Throwable e) { Log.e(TAG, "Error retrieving information for application " + appInfo.packageName); } } // clean uninstalled applications String[] newPackages = newPackagesList.toArray(new String[0]); Arrays.sort(newPackages); // simple way n * log(n) for (int i = 0; i < packagesSize; i++) { String packageName = packages[i]; if (Arrays.binarySearch(newPackages, packageName) < 0) { ContentResolver cr = context.getContentResolver(); cr.delete(Applications.Media.CONTENT_URI, ApplicationsColumns.PACKAGE_NAME + " LIKE '%" + packageName + "%'", null); } } } catch (Throwable e) { Log.e(TAG, "Error performing initial applications provider synchronization with device", e); } } private void syncMediaStoreSupport() { Set<File> ignorableFiles = Transfers.getIgnorableFiles(); syncMediaStore(Constants.FILE_TYPE_AUDIO, ignorableFiles); syncMediaStore(Constants.FILE_TYPE_PICTURES, ignorableFiles); syncMediaStore(Constants.FILE_TYPE_VIDEOS, ignorableFiles); syncMediaStore(Constants.FILE_TYPE_RINGTONES, ignorableFiles); scan(SystemPaths.getSaveDirectory(Constants.FILE_TYPE_DOCUMENTS)); } private void syncMediaStore(byte fileType, Set<File> ignorableFiles) { TableFetcher fetcher = TableFetchers.getFetcher(fileType); Cursor c = null; try { ContentResolver cr = context.getContentResolver(); String where = MediaColumns.DATA + " LIKE ?"; String[] whereArgs = new String[]{SystemPaths.getAppStorage().getAbsolutePath() + "%"}; c = cr.query(fetcher.getContentUri(), new String[]{MediaColumns._ID, MediaColumns.DATA}, where, whereArgs, null); if (c == null) { return; } int idCol = c.getColumnIndex(MediaColumns._ID); int pathCol = c.getColumnIndex(MediaColumns.DATA); List<Integer> ids = new ArrayList<Integer>(); while (c.moveToNext()) { int id = Integer.valueOf(c.getString(idCol)); String path = c.getString(pathCol); if (ignorableFiles.contains(new File(path))) { ids.add(id); } } cr.delete(fetcher.getContentUri(), MediaColumns._ID + " IN " + StringUtils.buildSet(ids), null); } catch (Throwable e) { Log.e(TAG, "General failure during sync of MediaStore", e); } finally { if (c != null) { c.close(); } } } private List<FileDescriptor> getFiles(int offset, int pageSize, TableFetcher fetcher, boolean sharedOnly) { return getFiles(offset, pageSize, fetcher, null, null, sharedOnly); } /** * Returns a list of Files. * * @param offset * - from where (starting at 0) * @param pageSize * - how many results * @param fetcher * - An implementation of TableFetcher * @param sharedOnly * - if true, retrieves only the fine grained shared files. * * @return List<FileDescriptor> */ private List<FileDescriptor> getFiles(int offset, int pageSize, TableFetcher fetcher, String where, String[] whereArgs, boolean sharedOnly) { List<FileDescriptor> result = new ArrayList<FileDescriptor>(); Cursor c = null; Set<Integer> sharedIds = getSharedFiles(fetcher.getFileType()); try { ContentResolver cr = context.getContentResolver(); String[] columns = fetcher.getColumns(); String sort = fetcher.getSortByExpression(); c = cr.query(fetcher.getContentUri(), columns, where, whereArgs, sort); if (c == null || !c.moveToPosition(offset)) { return result; } fetcher.prepare(c); int count = 1; do { FileDescriptor fd = fetcher.fetch(c); fd.shared = sharedIds.contains(fd.id); if (sharedOnly && !fd.shared) { continue; } result.add(fd); } while (c.moveToNext() && count++ < pageSize); } catch (Throwable e) { Log.e(TAG, "General failure getting files", e); } finally { if (c != null) { c.close(); } } return result; } public List<FileDescriptor> getFiles(String filepath, boolean exactPathMatch) { return getFiles(getFileType(filepath, true), filepath, exactPathMatch); } /** * * @param filepath * @param exactPathMatch - set it to false and pass an incomplete filepath prefix to get files in a folder for example. * @return */ public List<FileDescriptor> getFiles(byte fileType, String filepath, boolean exactPathMatch) { String where = MediaColumns.DATA + " LIKE ?"; String[] whereArgs = new String[] { (exactPathMatch) ? filepath : "%" + filepath + "%" }; List<FileDescriptor> fds = Librarian.instance().getFiles(fileType, where, whereArgs); return fds; } private Pair<List<Integer>, List<String>> getAllFiles(byte fileType) { Pair<List<Integer>, List<String>> result = new Pair<List<Integer>, List<String>>(new ArrayList<Integer>(), new ArrayList<String>()); Cursor c = null; try { TableFetcher fetcher = TableFetchers.getFetcher(fileType); ContentResolver cr = context.getContentResolver(); c = cr.query(fetcher.getContentUri(), new String[] { BaseColumns._ID, MediaColumns.DATA }, null, null, BaseColumns._ID); if (c != null) { while (c.moveToNext()) { result.first.add(c.getInt(0)); result.second.add(c.getString(1)); } } } catch (Throwable e) { Log.e(TAG, "General failure getting all files", e); } finally { if (c != null) { c.close(); } } return result; } private Set<Integer> getSharedFiles(byte fileType) { TreeSet<Integer> result = new TreeSet<Integer>(); List<Integer> delete = new ArrayList<Integer>(); Cursor c = null; try { ContentResolver cr = context.getContentResolver(); String[] columns = new String[] { SharingColumns.FILE_ID, SharingColumns._ID }; c = cr.query(Sharing.Media.CONTENT_URI, columns, SharingColumns.SHARED + "=1 AND " + SharingColumns.FILE_TYPE + "=?", new String[] { String.valueOf(fileType) }, null); if (c == null || !c.moveToFirst()) { return result; } int fileIdCol = c.getColumnIndex(SharingColumns.FILE_ID); int sharingIdCol = c.getColumnIndex(SharingColumns._ID); Pair<List<Integer>, List<String>> pair = getAllFiles(fileType); List<Integer> files = pair.first; List<String> paths = pair.second; do { int fileId = c.getInt(fileIdCol); int sharingId = c.getInt(sharingIdCol); int index = Collections.binarySearch(files, fileId); try { if (index >= 0) { File f = new File(paths.get(index)); if (f.exists() && f.isFile()) { result.add(fileId); } else { delete.add(sharingId); } } else { delete.add(sharingId); } } catch (Throwable e) { Log.e(TAG, "Error checking fileId: " + fileId + ", fileType: " + fileId); } } while (c.moveToNext()); } catch (Throwable e) { Log.e(TAG, "General failure getting shared/unshared files ids", e); } finally { if (c != null) { c.close(); } if (delete.size() > 0) { deleteSharedStates(delete); } } return result; } private void deleteSharedState(byte fileType, int fileId) { try { ContentResolver cr = context.getContentResolver(); int deleted = cr.delete(UniversalStore.Sharing.Media.CONTENT_URI, SharingColumns.FILE_ID + "= ? AND " + SharingColumns.FILE_TYPE + " = ?", new String[] { String.valueOf(fileId), String.valueOf(fileType) }); Log.d(TAG, "deleteSharedState " + deleted + " rows (fileType: " + fileType + ", fileId: " + fileId + " )"); } catch (Throwable e) { Log.e(TAG, "Failed to delete shared state for fileType=" + fileType + ", fileId=" + fileId, e); } } private void deleteSharedStates(List<Integer> sharingIds) { try { ContentResolver cr = context.getContentResolver(); int deleted = cr.delete(UniversalStore.Sharing.Media.CONTENT_URI, SharingColumns._ID + " IN " + StringUtils.buildSet(sharingIds), null); Log.d(TAG, "Deleted " + deleted + " shared states"); } catch (Throwable e) { Log.e(TAG, "Failed to delete shared states", e); } } /** * Updates the number of files for this type. * @param fileType */ private void updateCacheNumFiles(byte fileType, int num, boolean sharedOnly) { if (sharedOnly) { cache[fileType].updateShared(num); } else { cache[fileType].updateOnDisk(num); } } /** * This function returns an array of string in the following order: version name, label * @param apk * @return */ private String[] parseApk(Apk apk) { try { String[] result = new String[3]; XmlResourceParser parser = apk.getAndroidManifest(); boolean manifestParsed = true; boolean applicationParsed = false; while (!manifestParsed || !applicationParsed) { int type = parser.next(); if (type == XmlPullParser.END_DOCUMENT) { break; } switch (type) { case XmlPullParser.START_TAG: String tagName = parser.getName(); if (tagName.equals("manifest")) { String versionName = parser.getAttributeValue("http://schemas.android.com/apk/res/android", "versionName"); if (versionName != null && versionName.startsWith("@")) { versionName = apk.getString(Integer.parseInt(versionName.substring(1))); } result[0] = versionName; manifestParsed = true; } if (tagName.equals("application")) { String label = parser.getAttributeValue("http://schemas.android.com/apk/res/android", "label"); if (label != null && label.startsWith("@")) { label = apk.getString(Integer.parseInt(label.substring(1))); } result[1] = label; applicationParsed = true; } break; } } parser.close(); return result; } catch (Throwable e) { return null; } } private void scan(File file, Set<File> ignorableFiles) { //if we just have a single file, do it the old way if (file.isFile()) { if (ignorableFiles.contains(file)) { return; } new UniversalScanner(context).scan(file.getAbsolutePath()); } else if (file.isDirectory() && file.canRead()) { Collection<File> flattenedFiles = DirectoryUtils.getAllFolderFiles(file, null); if (ignorableFiles != null && !ignorableFiles.isEmpty()) { flattenedFiles.removeAll(ignorableFiles); } if (flattenedFiles != null && !flattenedFiles.isEmpty()) { new UniversalScanner(context).scan(flattenedFiles); } } } public ScreenMetrics readScreenMetrics() { ScreenMetrics sm = new ScreenMetrics(); try { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); context.getResources().getDisplayMetrics(); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); sm.densityDpi = dm.densityDpi; sm.heightPixels = dm.heightPixels; sm.widthPixels = dm.widthPixels; sm.xdpi = dm.xdpi; sm.ydpi = dm.ydpi; } catch (Throwable e) { Log.e(TAG, "Unable to get the device display dimensions", e); } return sm; } public double getScreenSizeInInches() { double screenInches = 0; try { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); context.getResources().getDisplayMetrics(); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); double x = Math.pow(dm.widthPixels / dm.xdpi, 2); double y = Math.pow(dm.heightPixels / dm.ydpi, 2); screenInches = Math.sqrt(x + y); } catch (Throwable e) { Log.e(TAG, "Unable to get the device display dimensions", e); } return screenInches; } private static class FileCountCache { public int shared; public int onDisk; public long lastTimeCachedShared; public long lastTimeCachedOnDisk; public FileCountCache() { shared = 0; onDisk = 0; lastTimeCachedShared = 0; lastTimeCachedOnDisk = 0; } public void updateShared(int num) { shared = num; lastTimeCachedShared = System.currentTimeMillis(); } public void updateOnDisk(int num) { onDisk = num; lastTimeCachedOnDisk = System.currentTimeMillis(); } public int getCount(boolean onlyShared) { return (onlyShared) ? shared : onDisk; } public boolean cacheValid(boolean onlyShared) { long delta = System.currentTimeMillis() - ((onlyShared) ? lastTimeCachedShared : lastTimeCachedOnDisk); return delta < Constants.LIBRARIAN_FILE_COUNT_CACHE_TIMEOUT; } } private FileDescriptor getFileDescriptor(File f) { FileDescriptor fd = null; if (f.exists()) { List<FileDescriptor> files = getFiles(f.getAbsolutePath(), false); if (!files.isEmpty()) { fd = files.get(0); } } return fd; } public FileDescriptor getFileDescriptor(Uri uri) { FileDescriptor fd = null; try { if (uri != null) { if (uri.toString().startsWith("file://")) { fd = getFileDescriptor(new File(uri.getPath())); } else { TableFetcher fetcher = TableFetchers.getFetcher(uri); fd = new FileDescriptor(); fd.fileType = fetcher.getFileType(); fd.id = Integer.valueOf(uri.getLastPathSegment()); } } } catch (Throwable e) { fd = null; // sometimes uri.getLastPathSegment() is not an integer e.printStackTrace(); } return fd; } private byte getFileType(String filename, boolean returnTorrentsAsDocument) { byte result = Constants.FILE_TYPE_DOCUMENTS; MediaType mt = MediaType.getMediaTypeForExtension(FilenameUtils.getExtension(filename)); if (mt != null) { result = (byte) mt.getId(); } if (returnTorrentsAsDocument && result == Constants.FILE_TYPE_TORRENTS) { result = Constants.FILE_TYPE_DOCUMENTS; } return result; } }