Merge branch 'master' into 'master'

send any installed app via NFC/Beam or Bluetooth

Building upon the NFC+Bluetooth sending of the FDroid.apk, these two commits allow the user to send any installed app via Bluetooth or NFC/Android Beam.
This commit is contained in:
Peter Serwylo 2014-04-22 15:28:29 +00:00
commit e3e726e56c
7 changed files with 170 additions and 107 deletions

View File

@ -96,9 +96,8 @@
</activity>
<activity
android:name=".ManageRepo"
android:allowTaskReparenting="true"
android:label="@string/menu_manage"
android:launchMode="singleTop"
android:launchMode="singleTask"
android:parentActivityName=".FDroid" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"

View File

@ -78,6 +78,7 @@
<string name="go_to_nfc_settings">Go to NFC Settings…</string>
<string name="bluetooth_activity_not_found">No Bluetooth send method found, choose one!</string>
<string name="choose_bt_send">Choose Bluetooth send method</string>
<string name="send_via_bluetooth">Send via Bluetooth</string>
<string name="repo_add_url">Repository address</string>
<string name="repo_add_fingerprint">Fingerprint (optional)</string>

View File

@ -19,11 +19,6 @@
package org.fdroid.fdroid;
import java.io.File;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.List;
import android.content.*;
import android.widget.*;
import org.fdroid.fdroid.data.*;
@ -33,12 +28,17 @@ import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v4.view.MenuItemCompat;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
@ -58,26 +58,31 @@ import android.view.View;
import android.view.ViewGroup;
import android.graphics.Bitmap;
import android.support.v4.app.NavUtils;
import android.support.v4.view.MenuItemCompat;
import org.fdroid.fdroid.compat.PackageManagerCompat;
import org.fdroid.fdroid.compat.ActionBarCompat;
import org.fdroid.fdroid.compat.MenuManager;
import org.fdroid.fdroid.Utils.CommaSeparatedList;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import org.fdroid.fdroid.Utils.CommaSeparatedList;
import org.fdroid.fdroid.compat.ActionBarCompat;
import org.fdroid.fdroid.compat.MenuManager;
import org.fdroid.fdroid.compat.PackageManagerCompat;
import java.io.File;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.List;
public class AppDetails extends ListActivity {
private static final String TAG = "AppDetails";
private static final int REQUEST_INSTALL = 0;
private static final int REQUEST_UNINSTALL = 1;
public static final int REQUEST_ENABLE_BLUETOOTH = 2;
public static final String EXTRA_APPID = "appid";
public static final String EXTRA_FROM = "from";
private FDroidApp fdroidApp;
private ApkListAdapter adapter;
private static class ViewHolder {
@ -234,6 +239,7 @@ public class AppDetails extends ListActivity {
private static final int DOGECOIN = Menu.FIRST + 12;
private static final int FLATTR = Menu.FIRST + 13;
private static final int DONATE_URL = Menu.FIRST + 14;
private static final int SEND_VIA_BLUETOOTH = Menu.FIRST + 15;
private App app;
private String appid;
@ -253,7 +259,8 @@ public class AppDetails extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
((FDroidApp) getApplication()).applyTheme(this);
fdroidApp = ((FDroidApp) getApplication());
fdroidApp.applyTheme(this);
super.onCreate(savedInstanceState);
@ -624,11 +631,14 @@ public class AppDetails extends ListActivity {
adapter.notifyDataSetChanged();
TextView tv = (TextView) findViewById(R.id.status);
if (!app.isInstalled())
tv.setText(getString(R.string.details_notinstalled));
else
if (app.isInstalled()) {
tv.setText(getString(R.string.details_installed,
app.installedVersionName));
NfcBeamManager.setAndroidBeam(this, app.id);
} else {
tv.setText(getString(R.string.details_notinstalled));
NfcBeamManager.disableAndroidBeam(this);
}
tv = (TextView) infoView.findViewById(R.id.signature);
if (pref_expert && mInstalledSignature != null) {
@ -755,6 +765,9 @@ public class AppDetails extends ListActivity {
if (app.donateURL != null)
donate.add(Menu.NONE, DONATE_URL, 10, R.string.menu_website);
}
if (app.isInstalled() && fdroidApp.bluetoothAdapter != null) { // ignore on devices without Bluetooth
menu.add(Menu.NONE, SEND_VIA_BLUETOOTH, 6, R.string.send_via_bluetooth);
}
return true;
}
@ -845,6 +858,17 @@ public class AppDetails extends ListActivity {
tryOpenUri(app.donateURL);
return true;
case SEND_VIA_BLUETOOTH:
/*
* If Bluetooth has not been enabled/turned on, then
* enabling device discoverability will automatically enable Bluetooth
*/
Intent discoverBt = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverBt.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 121);
startActivityForResult(discoverBt, REQUEST_ENABLE_BLUETOOTH);
// if this is successful, the Bluetooth transfer is started
return true;
}
return super.onOptionsItemSelected(item);
}
@ -1099,7 +1123,8 @@ public class AppDetails extends ListActivity {
case REQUEST_UNINSTALL:
resetRequired = true;
break;
case REQUEST_ENABLE_BLUETOOTH:
fdroidApp.sendViaBluetooth(this, resultCode, app.id);
}
}
}

View File

@ -20,18 +20,15 @@
package org.fdroid.fdroid;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.NotificationManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.*;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.net.Uri;
@ -48,7 +45,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import org.fdroid.fdroid.compat.TabManager;
import org.fdroid.fdroid.data.AppProvider;
@ -70,8 +66,7 @@ public class FDroid extends FragmentActivity {
private static final int SEARCH = Menu.FIRST + 4;
private static final int BLUETOOTH_APK = Menu.FIRST + 5;
/* request codes for Bluetooth flows */
private BluetoothAdapter mBluetoothAdapter = null;
private FDroidApp fdroidApp = null;
private ViewPager viewPager;
@ -80,7 +75,8 @@ public class FDroid extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
((FDroidApp) getApplication()).applyTheme(this);
fdroidApp = ((FDroidApp) getApplication());
fdroidApp.applyTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.fdroid);
@ -112,26 +108,13 @@ public class FDroid extends FragmentActivity {
Uri uri = AppProvider.getContentUri();
getContentResolver().registerContentObserver(uri, true, new AppObserver());
getBluetoothAdapter();
}
@TargetApi(18)
private void 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)
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
else
mBluetoothAdapter = ((BluetoothManager) getSystemService(BLUETOOTH_SERVICE)).getAdapter();
}
@Override
protected void onResume() {
super.onResume();
// RepoDetailsActivity sets a different beam, so reset here
if (Build.VERSION.SDK_INT >= 16)
setupAndroidBeam();
// AppDetails and RepoDetailsActivity set different NFC actions, so reset here
NfcBeamManager.setAndroidBeam(this, getApplication().getPackageName());
}
@Override
@ -150,7 +133,7 @@ public class FDroid extends FragmentActivity {
android.R.drawable.ic_menu_agenda);
MenuItem search = menu.add(Menu.NONE, SEARCH, 3, R.string.menu_search).setIcon(
android.R.drawable.ic_menu_search);
if (mBluetoothAdapter != null) // ignore on devices without Bluetooth
if (fdroidApp.bluetoothAdapter != null) // ignore on devices without Bluetooth
menu.add(Menu.NONE, BLUETOOTH_APK, 3, R.string.menu_send_apk_bt);
menu.add(Menu.NONE, PREFERENCES, 4, R.string.menu_preferences).setIcon(
android.R.drawable.ic_menu_preferences);
@ -299,45 +282,7 @@ public class FDroid extends FragmentActivity {
}
break;
case REQUEST_ENABLE_BLUETOOTH:
if (resultCode == Activity.RESULT_CANCELED)
break;
String packageName = null;
String className = null;
boolean found = false;
Intent sendBt = null;
try {
PackageManager pm = getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo("org.fdroid.fdroid",
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)) {
packageName = info.activityInfo.packageName;
if (packageName.equals("com.android.bluetooth")
|| packageName.equals("com.mediatek.bluetooth")) {
className = info.activityInfo.name;
found = true;
break;
}
}
} catch (NameNotFoundException e1) {
e1.printStackTrace();
found = false;
}
if (!found) {
Toast.makeText(this, R.string.bluetooth_activity_not_found,
Toast.LENGTH_SHORT).show();
startActivity(Intent.createChooser(sendBt, getString(R.string.choose_bt_send)));
} else {
sendBt.setClassName(packageName, className);
startActivity(sendBt);
}
fdroidApp.sendViaBluetooth(this, resultCode, "org.fdroid.fdroid");
break;
}
}
@ -422,23 +367,4 @@ public class FDroid extends FragmentActivity {
}
@TargetApi(16)
private void setupAndroidBeam() {
PackageManager pm = getPackageManager();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter != null) {
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo("org.fdroid.fdroid",
PackageManager.GET_META_DATA);
// TODO can we send the repo here also, as a file?
Uri uris[] = {
Uri.parse("file://" + appInfo.publicSourceDir),
};
nfcAdapter.setBeamPushUris(uris, this);
} catch (NameNotFoundException e1) {
e1.printStackTrace();
}
}
}
}

View File

@ -18,17 +18,31 @@
package org.fdroid.fdroid;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiscCache;
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 de.duenndns.ssl.MemorizingTrustManager;
import org.fdroid.fdroid.compat.PRNGFixes;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
@ -44,6 +58,8 @@ import java.security.NoSuchAlgorithmException;
public class FDroidApp extends Application {
BluetoothAdapter bluetoothAdapter = null;
private static enum Theme {
dark, light
}
@ -121,6 +137,7 @@ public class FDroidApp extends Application {
}
UpdateService.schedule(getApplicationContext());
bluetoothAdapter = getBluetoothAdapter();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
.discCache(new LimitedAgeDiscCache(
@ -179,4 +196,55 @@ public class FDroidApp extends Application {
}
}
@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();
else
return ((BluetoothManager) getSystemService(BLUETOOTH_SERVICE)).getAdapter();
}
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 (bluetoothPackageName.equals("com.android.bluetooth")
|| bluetoothPackageName.equals("com.mediatek.bluetooth")) {
className = info.activityInfo.name;
found = true;
break;
}
}
} catch (NameNotFoundException e1) {
e1.printStackTrace();
found = false;
}
if (!found) {
Toast.makeText(this, R.string.bluetooth_activity_not_found,
Toast.LENGTH_SHORT).show();
activity.startActivity(Intent.createChooser(sendBt, getString(R.string.choose_bt_send)));
} else {
sendBt.setClassName(bluetoothPackageName, className);
activity.startActivity(sendBt);
}
}
}

View File

@ -0,0 +1,43 @@
package org.fdroid.fdroid;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Build;
@TargetApi(16)
public class NfcBeamManager {
static void setAndroidBeam(Activity activity, String packageName) {
if (Build.VERSION.SDK_INT < 16)
return;
PackageManager pm = activity.getPackageManager();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
if (nfcAdapter != null) {
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
Uri uris[] = {
Uri.parse("file://" + appInfo.publicSourceDir),
};
nfcAdapter.setBeamPushUris(uris, activity);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
}
static void disableAndroidBeam(Activity activity) {
if (Build.VERSION.SDK_INT < 16)
return;
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
if (nfcAdapter != null)
nfcAdapter.setBeamPushUris(null, activity);
}
}

View File

@ -69,14 +69,12 @@ public class RepoDetailsActivity extends FragmentActivity {
setTitle(repo.getName());
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
// required NFC support starts in android-14
if (Build.VERSION.SDK_INT >= 14)
setNfc();
}
@TargetApi(14)
private void setNfc() {
if (Build.VERSION.SDK_INT < 14)
return;
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
return;
@ -97,8 +95,9 @@ public class RepoDetailsActivity extends FragmentActivity {
public void onResume() {
Log.i(TAG, "onResume");
super.onResume();
if (Build.VERSION.SDK_INT >= 9)
processIntent(getIntent());
// FDroid.java and AppDetails set different NFC actions, so reset here
setNfc();
processIntent(getIntent());
}
@Override
@ -112,6 +111,8 @@ public class RepoDetailsActivity extends FragmentActivity {
@TargetApi(9)
void processIntent(Intent i) {
if (Build.VERSION.SDK_INT < 9)
return;
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(i.getAction())) {
Log.i(TAG, "ACTION_NDEF_DISCOVERED");
Parcelable[] rawMsgs =