diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ac8ee383a..b23692682 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -83,6 +83,18 @@ + + + + + + + + + + + + diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 7759bf80e..7f25bff85 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -433,14 +433,14 @@ public class AppDetails extends ListActivity { The following is a quick solution to enable both text selection and links. Causes glitches and crashes: java.lang.IndexOutOfBoundsException: setSpan (-1 ... -1) starts before 0 - + class CustomMovementMethod extends LinkMovementMethod { @Override public boolean canSelectArbitrarily () { return true; } } - + if (Utils.hasApi(11)) { tv.setTextIsSelectable(true); tv.setMovementMethod(new CustomMovementMethod()); @@ -598,6 +598,7 @@ public class AppDetails extends ListActivity { ask_alrt.setMessage(getString(R.string.installDowngrade)); ask_alrt.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { install(); @@ -605,6 +606,7 @@ public class AppDetails extends ListActivity { }); ask_alrt.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { return; @@ -807,6 +809,7 @@ public class AppDetails extends ListActivity { ask_alrt.setMessage(getString(R.string.installIncompatible)); ask_alrt.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { downloadHandler = new DownloadHandler(app.curApk, @@ -816,6 +819,7 @@ public class AppDetails extends ListActivity { }); ask_alrt.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { return; @@ -831,6 +835,7 @@ public class AppDetails extends ListActivity { builder.setMessage(R.string.SignatureMismatch).setPositiveButton( getString(R.string.ok), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } @@ -890,6 +895,7 @@ public class AppDetails extends ListActivity { pd.setCancelable(true); pd.setCanceledOnTouchOutside(false); pd.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override public void onCancel(DialogInterface dialog) { downloadHandler.cancel(); } @@ -897,6 +903,7 @@ public class AppDetails extends ListActivity { pd.setButton(DialogInterface.BUTTON_NEUTRAL, getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { pd.cancel(); } diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java index 531c22d04..3b950bd5d 100644 --- a/src/org/fdroid/fdroid/DB.java +++ b/src/org/fdroid/fdroid/DB.java @@ -19,11 +19,14 @@ package org.fdroid.fdroid; +import android.annotation.SuppressLint; import java.io.File; +import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.Formatter; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -350,6 +353,7 @@ public class DB { } private static class BasicChecker extends CompatibilityChecker { + @Override public boolean isCompatible(Apk apk) { return hasApi(apk.minSdkVersion); } @@ -362,6 +366,7 @@ public class DB { private List cpuAbis; private boolean ignoreTouchscreen; + @SuppressLint("NewApi") public EclairChecker(Context ctx) { SharedPreferences prefs = PreferenceManager @@ -397,6 +402,7 @@ public class DB { return false; } + @Override public boolean isCompatible(Apk apk) { if (!hasApi(apk.minSdkVersion)) return false; @@ -430,7 +436,8 @@ public class DB { private static final String CREATE_TABLE_REPO = "create table " + TABLE_REPO + " (id integer primary key, address text not null, " + "name text, description text, inuse integer not null, " - + "priority integer not null, pubkey text, lastetag text);"; + + "priority integer not null, pubkey text, fingerprint text, " + + "lastetag text);"; public static class Repo { public int id; @@ -440,10 +447,11 @@ public class DB { public boolean inuse; public int priority; public String pubkey; // null for an unsigned repo + public String fingerprint; // always null for an unsigned repo public String lastetag; // last etag we updated from, null forces update } - private final int DBVersion = 28; + private final int DBVersion = 29; private static void createAppApk(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_APP); @@ -453,6 +461,26 @@ public class DB { db.execSQL("create index apk_id on " + TABLE_APK + " (id);"); } + public static String calcFingerprint(String pubkey) { + String ret = null; + try { + // keytool -list -v gives you the SHA-256 fingerprint + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(Hasher.unhex(pubkey)); + byte[] fingerprint = digest.digest(); + Formatter formatter = new Formatter(new StringBuilder()); + for (int i = 1; i < fingerprint.length; i++) { + formatter.format("%02X", fingerprint[i]); + } + ret = formatter.toString(); + formatter.close(); + } catch (Exception e) { + Log.w("FDroid", "Unable to get certificate fingerprint.\n" + + Log.getStackTraceString(e)); + } + return ret; + } + public void resetTransient(SQLiteDatabase db) { mContext.getSharedPreferences("FDroid", Context.MODE_PRIVATE).edit() .putBoolean("triedEmptyUpdate", false).commit(); @@ -487,8 +515,10 @@ public class DB { mContext.getString(R.string.default_repo_name)); values.put("description", mContext.getString(R.string.default_repo_description)); - values.put("pubkey", - mContext.getString(R.string.default_repo_pubkey)); + String pubkey = mContext.getString(R.string.default_repo_pubkey); + String fingerprint = DB.calcFingerprint(pubkey); + values.put("pubkey", pubkey); + values.put("fingerprint", fingerprint); values.put("inuse", 1); values.put("priority", 10); values.put("lastetag", (String) null); @@ -501,8 +531,9 @@ public class DB { mContext.getString(R.string.default_repo_name2)); values.put("description", mContext.getString(R.string.default_repo_description2)); - values.put("pubkey", - mContext.getString(R.string.default_repo_pubkey)); + // default #2 is /archive which has the same key as /repo + values.put("pubkey", pubkey); + values.put("fingerprint", fingerprint); values.put("inuse", 0); values.put("priority", 20); values.put("lastetag", (String) null); @@ -564,6 +595,28 @@ public class DB { mContext.getString(R.string.default_repo_address2) }); } + if (oldVersion < 29) { + if (!columnExists(db, TABLE_REPO, "fingerprint")) + db.execSQL("alter table " + TABLE_REPO + " add column fingerprint text"); + List oldrepos = new ArrayList(); + Cursor c = db.query(TABLE_REPO, + new String[] { "address", "pubkey" }, + null, null, null, null, null); + c.moveToFirst(); + while (!c.isAfterLast()) { + Repo repo = new Repo(); + repo.address = c.getString(0); + repo.pubkey = c.getString(1); + oldrepos.add(repo); + c.moveToNext(); + } + c.close(); + for (Repo repo : oldrepos) { + ContentValues values = new ContentValues(); + values.put("fingerprint", DB.calcFingerprint(repo.pubkey)); + db.update(TABLE_REPO, values, "address = ?", new String[] { repo.address }); + } + } } } @@ -1002,10 +1055,12 @@ public class DB { return (instance == null ? null : instance.toString()); } + @Override public String toString() { return value; } + @Override public Iterator iterator() { SimpleStringSplitter splitter = new SimpleStringSplitter(','); splitter.setString(value); @@ -1239,7 +1294,8 @@ public class DB { Cursor c = null; try { c = db.query(TABLE_REPO, new String[] { "address", "name", - "description", "inuse", "priority", "pubkey", "lastetag" }, + "description", "inuse", "priority", "pubkey", "fingerprint", + "lastetag" }, "id = ?", new String[] { Integer.toString(id) }, null, null, null); if (!c.moveToFirst()) return null; @@ -1251,7 +1307,8 @@ public class DB { repo.inuse = (c.getInt(3) == 1); repo.priority = c.getInt(4); repo.pubkey = c.getString(5); - repo.lastetag = c.getString(6); + repo.fingerprint = c.getString(6); + repo.lastetag = c.getString(7); return repo; } finally { if (c != null) @@ -1265,7 +1322,8 @@ public class DB { Cursor c = null; try { c = db.query(TABLE_REPO, new String[] { "id", "address", "name", - "description", "inuse", "priority", "pubkey", "lastetag" }, + "description", "inuse", "priority", "pubkey", "fingerprint", + "lastetag" }, null, null, null, null, "priority"); c.moveToFirst(); while (!c.isAfterLast()) { @@ -1277,7 +1335,8 @@ public class DB { repo.inuse = (c.getInt(4) == 1); repo.priority = c.getInt(5); repo.pubkey = c.getString(6); - repo.lastetag = c.getString(7); + repo.fingerprint = c.getString(7); + repo.lastetag = c.getString(8); repos.add(repo); c.moveToNext(); } @@ -1310,6 +1369,12 @@ public class DB { values.put("inuse", repo.inuse); values.put("priority", repo.priority); values.put("pubkey", repo.pubkey); + if (repo.pubkey != null && repo.fingerprint == null) { + // we got a new pubkey, so calc the fingerprint + values.put("fingerprint", DB.calcFingerprint(repo.pubkey)); + } else { + values.put("fingerprint", repo.fingerprint); + } values.put("lastetag", (String) null); db.update(TABLE_REPO, values, "address = ?", new String[] { repo.address }); @@ -1323,7 +1388,8 @@ public class DB { } public void addRepo(String address, String name, String description, - int priority, String pubkey, boolean inuse) { + int priority, String pubkey, String fingerprint, boolean inuse) + throws SecurityException { ContentValues values = new ContentValues(); values.put("address", address); values.put("name", name); @@ -1331,6 +1397,17 @@ public class DB { values.put("inuse", inuse ? 1 : 0); values.put("priority", priority); values.put("pubkey", pubkey); + String calcedFingerprint = DB.calcFingerprint(pubkey); + if (fingerprint == null) { + fingerprint = calcedFingerprint; + } else { + fingerprint = fingerprint.toUpperCase(); + if (!fingerprint.equals(calcedFingerprint)) { + throw new SecurityException("Given fingerprint does not match calculated one! (" + + fingerprint + " != " + calcedFingerprint); + } + } + values.put("fingerprint", fingerprint); values.put("lastetag", (String) null); db.insert(TABLE_REPO, null, values); } diff --git a/src/org/fdroid/fdroid/Downloader.java b/src/org/fdroid/fdroid/Downloader.java index ca7a5a987..e0cb96ad6 100644 --- a/src/org/fdroid/fdroid/Downloader.java +++ b/src/org/fdroid/fdroid/Downloader.java @@ -95,6 +95,7 @@ public class Downloader extends Thread { return curapk; } + @Override public void run() { InputStream input = null; diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java index 5068adada..63353ffec 100644 --- a/src/org/fdroid/fdroid/FDroid.java +++ b/src/org/fdroid/fdroid/FDroid.java @@ -23,6 +23,7 @@ import android.content.*; import android.content.res.Configuration; import android.support.v4.view.MenuItemCompat; +import android.annotation.TargetApi; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.NotificationManager; @@ -123,6 +124,7 @@ public class FDroid extends FragmentActivity { manager.repopulateLists(); } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); getTabManager().onConfigurationChanged(newConfig); @@ -205,6 +207,7 @@ public class FDroid extends FragmentActivity { alrt.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.about_website), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { Uri uri = Uri.parse("https://f-droid.org"); @@ -213,6 +216,7 @@ public class FDroid extends FragmentActivity { }); alrt.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.ok), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { } @@ -223,6 +227,7 @@ public class FDroid extends FragmentActivity { return super.onOptionsItemSelected(item); } + @TargetApi(5) @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -237,6 +242,7 @@ public class FDroid extends FragmentActivity { ask_alrt.setMessage(getString(R.string.repo_alrt)); ask_alrt.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { updateRepos(); @@ -244,6 +250,7 @@ public class FDroid extends FragmentActivity { }); ask_alrt.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { return; @@ -264,7 +271,7 @@ public class FDroid extends FragmentActivity { } else if ((resultCode & PreferencesActivity.RESULT_REFILTER) != 0) { ((FDroidApp) getApplication()).filterApps(); } - + if ((resultCode & PreferencesActivity.RESULT_RESTART) != 0) { ((FDroidApp) getApplication()).reloadTheme(); final Intent intent = getIntent(); @@ -284,6 +291,7 @@ public class FDroid extends FragmentActivity { viewPageAdapter = new AppListFragmentPageAdapter(this); viewPager.setAdapter(viewPageAdapter); viewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { + @Override public void onPageSelected(int position) { getTabManager().selectTab(position); } diff --git a/src/org/fdroid/fdroid/FDroidApp.java b/src/org/fdroid/fdroid/FDroidApp.java index 9d401f92c..b7dea120d 100644 --- a/src/org/fdroid/fdroid/FDroidApp.java +++ b/src/org/fdroid/fdroid/FDroidApp.java @@ -117,6 +117,7 @@ public class FDroidApp extends Application { .discCache(new UnlimitedDiscCache( new File(StorageUtils.getCacheDirectory(ctx), "icons"), new FileNameGenerator() { + @Override public String generate(String imageUri) { return imageUri.substring( imageUri.lastIndexOf('/') + 1); diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index fae936183..60ad9c656 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -19,10 +19,8 @@ package org.fdroid.fdroid; -import java.security.MessageDigest; import java.util.ArrayList; import java.util.Date; -import java.util.Formatter; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -157,23 +155,8 @@ public class ManageRepo extends ListActivity { } else { server_line.put("inuse", R.drawable.btn_check_off); } - if (repo.pubkey != null) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - digest.update(Hasher.unhex(repo.pubkey)); - byte[] fingerprint = digest.digest(); - Formatter formatter = new Formatter(new StringBuilder()); - formatter.format("%02X", fingerprint[0]); - for (int i = 1; i < fingerprint.length; i++) { - formatter.format(i % 5 == 0 ? " %02X" : ":%02X", - fingerprint[i]); - } - server_line.put("fingerprint", formatter.toString()); - formatter.close(); - } catch (Exception e) { - Log.w("FDroid", "Unable to get certificate fingerprint.\n" - + Log.getStackTraceString(e)); - } + if (repo.fingerprint != null) { + server_line.put("fingerprint", repo.fingerprint); } result.add(server_line); } @@ -202,6 +185,7 @@ public class ManageRepo extends ListActivity { redraw(); } + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); @@ -215,10 +199,10 @@ public class ManageRepo extends ListActivity { return true; } - protected void addRepo(String repoUri) { + protected void addRepo(String repoUri, String fingerprint) { try { DB db = DB.getDB(); - db.addRepo(repoUri, null, null, 10, null, true); + db.addRepo(repoUri, null, null, 10, null, fingerprint, true); } finally { DB.releaseDB(); } @@ -268,7 +252,8 @@ public class ManageRepo extends ListActivity { new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - addRepo(uriEditText.getText().toString()); + addRepo(uriEditText.getText().toString(), + fingerprintEditText.getText().toString()); changed = true; redraw(); } @@ -332,6 +317,7 @@ public class ManageRepo extends ListActivity { builder.setIcon(android.R.drawable.ic_menu_close_clear_cancel); builder.setMultiChoiceItems(b, null, new DialogInterface.OnMultiChoiceClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton, boolean isChecked) { if (isChecked) { @@ -343,6 +329,7 @@ public class ManageRepo extends ListActivity { }); builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { try { @@ -357,6 +344,7 @@ public class ManageRepo extends ListActivity { }); builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { return; diff --git a/src/org/fdroid/fdroid/UpdateService.java b/src/org/fdroid/fdroid/UpdateService.java index a7c07787c..5323d0e60 100644 --- a/src/org/fdroid/fdroid/UpdateService.java +++ b/src/org/fdroid/fdroid/UpdateService.java @@ -115,6 +115,7 @@ public class UpdateService extends IntentService implements ProgressListener { return count; } + @Override protected void onHandleIntent(Intent intent) { receiver = intent.getParcelableExtra("receiver"); diff --git a/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java b/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java index 575839865..cd8946490 100644 --- a/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java +++ b/src/org/fdroid/fdroid/views/AppListFragmentPageAdapter.java @@ -38,6 +38,7 @@ public class AppListFragmentPageAdapter extends FragmentPagerAdapter { return 3; } + @Override public String getPageTitle(int i) { switch(i) { case 0: diff --git a/src/org/fdroid/fdroid/views/fragments/AppListFragment.java b/src/org/fdroid/fdroid/views/fragments/AppListFragment.java index b8102f320..5177c4ac9 100644 --- a/src/org/fdroid/fdroid/views/fragments/AppListFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/AppListFragment.java @@ -32,6 +32,7 @@ abstract class AppListFragment extends Fragment implements AdapterView.OnItemCli Preferences.get().unregisterCompactLayoutChangeListener(this); } + @Override public void onAttach(Activity activity) { super.onAttach(activity); try { diff --git a/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java index f7cd4263d..26f34e363 100644 --- a/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java @@ -12,6 +12,7 @@ import org.fdroid.fdroid.views.AppListView; public class AvailableAppsFragment extends AppListFragment implements AdapterView.OnItemSelectedListener { + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { AppListView view = new AppListView(getActivity()); view.setOrientation(LinearLayout.VERTICAL); @@ -40,6 +41,7 @@ public class AvailableAppsFragment extends AppListFragment implements AdapterVie return view; } + @Override public void onItemSelected(AdapterView parent, View view, int pos, long id) { String category = parent.getItemAtPosition(pos).toString(); diff --git a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java index a233f61e7..a390f365c 100644 --- a/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/CanUpdateAppsFragment.java @@ -9,6 +9,7 @@ import org.fdroid.fdroid.views.AppListAdapter; public class CanUpdateAppsFragment extends AppListFragment { + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return createPlainAppList(); } diff --git a/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java b/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java index 8eb4bf04c..db3a93cfc 100644 --- a/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/InstalledAppsFragment.java @@ -9,6 +9,7 @@ import org.fdroid.fdroid.views.AppListAdapter; public class InstalledAppsFragment extends AppListFragment { + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return createPlainAppList(); }