Java tutorial
/* * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.jtechme.apphub; import android.annotation.TargetApi; import android.app.Activity; import android.app.Application; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.net.Uri; import android.os.Build; import android.os.StrictMode; import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiskCache; import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.utils.StorageUtils; import org.acra.ACRA; import org.acra.ReportingInteractionMode; import org.acra.annotation.ReportsCrashes; import org.apache.commons.net.util.SubnetUtils; import com.jtechme.apphub.Preferences.ChangeListener; import com.jtechme.apphub.compat.PRNGFixes; import com.jtechme.apphub.data.AppProvider; import com.jtechme.apphub.data.InstalledAppCacheUpdater; import com.jtechme.apphub.data.Repo; import com.jtechme.apphub.net.IconDownloader; import com.jtechme.apphub.net.WifiStateChangeService; import java.io.File; import java.net.URL; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.security.Security; import java.util.Locale; import sun.net.www.protocol.bluetooth.Handler; @ReportsCrashes(mailTo = "reports.apphub@gmail.com", mode = ReportingInteractionMode.DIALOG, reportDialogClass = CrashReportActivity.class) public class FDroidApp extends Application { private static final String TAG = "FDroidApp"; private static Locale locale; // for the local repo on this device, all static since there is only one public static int port; public static String ipAddressString; public static SubnetUtils.SubnetInfo subnetInfo; public static String ssid; public static String bssid; public static final Repo repo = new Repo(); // Leaving the fully qualified class name here to help clarify the difference between spongy/bouncy castle. private static final org.spongycastle.jce.provider.BouncyCastleProvider spongyCastleProvider; @SuppressWarnings("unused") BluetoothAdapter bluetoothAdapter; static { spongyCastleProvider = new org.spongycastle.jce.provider.BouncyCastleProvider(); enableSpongyCastle(); } public enum Theme { light, dark, night, lightWithDarkActionBar, // Obsolete } private static Theme curTheme = Theme.light; public void reloadTheme() { curTheme = Theme.valueOf(PreferenceManager.getDefaultSharedPreferences(getBaseContext()) .getString(Preferences.PREF_THEME, Preferences.DEFAULT_THEME)); } public void applyTheme(Activity activity) { activity.setTheme(getCurThemeResId()); } public static int getCurThemeResId() { switch (curTheme) { case light: return R.style.AppThemeLight; case dark: return R.style.AppThemeDark; case night: return R.style.AppThemeNight; default: return R.style.AppThemeLight; } } public static void enableSpongyCastle() { Security.addProvider(spongyCastleProvider); } public static void enableSpongyCastleOnLollipop() { if (Build.VERSION.SDK_INT == 21) { Security.addProvider(spongyCastleProvider); } } public static void disableSpongyCastleOnLollipop() { if (Build.VERSION.SDK_INT == 21) { Security.removeProvider(spongyCastleProvider.getName()); } } public static void initWifiSettings() { port = 8888; ipAddressString = null; subnetInfo = new SubnetUtils("0.0.0.0/32").getInfo(); ssid = ""; bssid = ""; } public void updateLanguage() { Context ctx = getBaseContext(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); String lang = prefs.getString(Preferences.PREF_LANGUAGE, ""); locale = Utils.getLocaleFromAndroidLangTag(lang); applyLanguage(); } private void applyLanguage() { Context ctx = getBaseContext(); Configuration cfg = new Configuration(); cfg.locale = locale == null ? Locale.getDefault() : locale; ctx.getResources().updateConfiguration(cfg, null); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); applyLanguage(); } @TargetApi(9) @Override public void onCreate() { if (Build.VERSION.SDK_INT >= 9 && BuildConfig.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build()); } updateLanguage(); super.onCreate(); ACRA.init(this); // Needs to be setup before anything else tries to access it. // Perhaps the constructor is a better place, but then again, // it is more deterministic as to when this gets called... Preferences.setup(this); // Apply the Google PRNG fixes to properly seed SecureRandom PRNGFixes.apply(); // Check that the installed app cache hasn't gotten out of sync somehow. // e.g. if we crashed/ran out of battery half way through responding // to a package installed intent. It doesn't really matter where // we put this in the bootstrap process, because it runs on a different // thread, which will be delayed by some seconds to avoid an error where // the database is locked due to the database updater. InstalledAppCacheUpdater.updateInBackground(getApplicationContext()); // If the user changes the preference to do with filtering rooted apps, // it is easier to just notify a change in the app provider, // so that the newly updated list will correctly filter relevant apps. Preferences.get().registerAppsRequiringRootChangeListener(new Preferences.ChangeListener() { @Override public void onPreferenceChange() { getContentResolver().notifyChange(AppProvider.getContentUri(), null); } }); // This is added so that the bluetooth:// scheme we use for URLs the BluetoothDownloader // understands is not treated as invalid by the java.net.URL class. The actual Handler does // nothing, but its presence is enough. URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() { @Override public URLStreamHandler createURLStreamHandler(String protocol) { return TextUtils.equals(protocol, "bluetooth") ? new Handler() : null; } }); final Context context = this; Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() { @Override public void onPreferenceChange() { AppProvider.Helper.calcDetailsFromIndex(context); } }); // Clear cached apk files. We used to just remove them after they'd // been installed, but this causes problems for proprietary gapps // users since the introduction of verification (on pre-4.2 Android), // because the install intent says it's finished when it hasn't. SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); curTheme = Theme.valueOf(prefs.getString(Preferences.PREF_THEME, Preferences.DEFAULT_THEME)); Utils.deleteFiles(Utils.getApkDownloadDir(this), null, ".apk"); if (!Preferences.get().shouldCacheApks()) { Utils.deleteFiles(Utils.getApkCacheDir(this), null, ".apk"); } // Index files which downloaded, but were not removed (e.g. due to F-Droid being force // closed during processing of the file, before getting a chance to delete). This may // include both "index-*-downloaded" and "index-*-extracted.xml" files. The first is from // either signed or unsigned repos, and the later is from signed repos. Utils.deleteFiles(getCacheDir(), "index-", null); // As above, but for legacy F-Droid clients that downloaded under a different name, and // extracted to the files directory rather than the cache directory. // TODO: This can be removed in a a few months or a year (e.g. 2016) because people will // have upgraded their clients, this code will have executed, and they will not have any // left over files any more. Even if they do hold off upgrading until this code is removed, // the only side effect is that they will have a few more MiB of storage taken up on their // device until they uninstall and re-install F-Droid. Utils.deleteFiles(getCacheDir(), "dl-", null); Utils.deleteFiles(getFilesDir(), "index-", null); UpdateService.schedule(getApplicationContext()); bluetoothAdapter = getBluetoothAdapter(); ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()) .imageDownloader(new IconDownloader(getApplicationContext())) .diskCache(new LimitedAgeDiskCache( new File(StorageUtils.getCacheDirectory(getApplicationContext(), true), "icons"), null, new FileNameGenerator() { @Override public String generate(String imageUri) { return imageUri.substring(imageUri.lastIndexOf('/') + 1); } }, // 30 days in secs: 30*24*60*60 = 2592000 2592000)) .threadPoolSize(4).threadPriority(Thread.NORM_PRIORITY - 2) // Default is NORM_PRIORITY - 1 .build(); ImageLoader.getInstance().init(config); // TODO reintroduce PinningTrustManager and MemorizingTrustManager // initialized the local repo information FDroidApp.initWifiSettings(); startService(new Intent(this, WifiStateChangeService.class)); // if the HTTPS pref changes, then update all affected things Preferences.get().registerLocalRepoHttpsListeners(new ChangeListener() { @Override public void onPreferenceChange() { startService(new Intent(FDroidApp.this, WifiStateChangeService.class)); } }); } @TargetApi(18) private BluetoothAdapter getBluetoothAdapter() { // to use the new, recommended way of getting the adapter // http://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html if (Build.VERSION.SDK_INT < 18) { return BluetoothAdapter.getDefaultAdapter(); } return ((BluetoothManager) getSystemService(BLUETOOTH_SERVICE)).getAdapter(); } public void sendViaBluetooth(Activity activity, int resultCode, String packageName) { if (resultCode == Activity.RESULT_CANCELED) return; String bluetoothPackageName = null; String className = null; boolean found = false; Intent sendBt = null; try { PackageManager pm = getPackageManager(); ApplicationInfo appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); sendBt = new Intent(Intent.ACTION_SEND); // The APK type is blocked by stock Android, so use zip // sendBt.setType("application/vnd.android.package-archive"); sendBt.setType("application/zip"); sendBt.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + appInfo.publicSourceDir)); // not all devices have the same Bluetooth Activities, so // let's find it for (ResolveInfo info : pm.queryIntentActivities(sendBt, 0)) { bluetoothPackageName = info.activityInfo.packageName; if ("com.android.bluetooth".equals(bluetoothPackageName) || "com.mediatek.bluetooth".equals(bluetoothPackageName)) { className = info.activityInfo.name; found = true; break; } } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Could not get application info to send via bluetooth", e); found = false; } if (sendBt != null) { if (found) { sendBt.setClassName(bluetoothPackageName, className); activity.startActivity(sendBt); } else { Toast.makeText(this, R.string.bluetooth_activity_not_found, Toast.LENGTH_SHORT).show(); activity.startActivity(Intent.createChooser(sendBt, getString(R.string.choose_bt_send))); } } } }