1
0
Fork 0
mirror of https://github.com/MatomoCamp/matomocamp-companion-android.git synced 2024-09-19 16:13:46 +02:00

Added Android Beam support to allow sharing sessions using NFC, in all

activities showing sessions.
This commit is contained in:
Christophe Beyls 2014-02-22 19:09:29 +01:00
parent 86e69f2856
commit f78b9576fc
8 changed files with 232 additions and 13 deletions

View file

@ -9,6 +9,7 @@
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<!-- Permissions required for alarms -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
@ -46,7 +47,15 @@
android:value=".activities.MainActivity" />
</activity>
<activity android:name=".activities.TrackScheduleEventActivity" />
<activity android:name=".activities.EventDetailsActivity" />
<activity android:name=".activities.EventDetailsActivity" >
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/be.digitalia.fosdem" />
</intent-filter>
</activity>
<activity android:name=".activities.PersonInfoActivity" />
<activity
android:name=".activities.SearchResultActivity"

View file

@ -51,6 +51,7 @@
<!-- Errors -->
<string name="error_title">Error</string>
<string name="event_not_found_error">Unable to load the session details.\nMake sure the database has been updated to the latest version.</string>
<string name="schedule_loading_error">An error occurred while updating the schedule. Please try again later.</string>
<string name="search_length_error">The minimum search text length is 3 chars.</string>

View file

@ -5,17 +5,20 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
import android.widget.Toast;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.fragments.EventDetailsFragment;
import be.digitalia.fosdem.loaders.LocalCacheLoader;
import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.utils.NfcUtils;
import be.digitalia.fosdem.utils.NfcUtils.CreateNfcAppDataCallback;
/**
* Displays a single event passed either as a complete Parcelable object in extras or as an id in data.
@ -23,7 +26,7 @@ import be.digitalia.fosdem.model.Event;
* @author Christophe Beyls
*
*/
public class EventDetailsActivity extends ActionBarActivity implements LoaderCallbacks<Event> {
public class EventDetailsActivity extends ActionBarActivity implements LoaderCallbacks<Event>, CreateNfcAppDataCallback {
public static final String EXTRA_EVENT = "event";
@ -38,11 +41,11 @@ public class EventDetailsActivity extends ActionBarActivity implements LoaderCal
getSupportActionBar().setTitle(R.string.event_details);
event = getIntent().getParcelableExtra(EXTRA_EVENT);
Event event = getIntent().getParcelableExtra(EXTRA_EVENT);
if (event != null) {
// The event has been passed as parameter, it can be displayed immediately
initActionBar();
initEvent(event);
if (savedInstanceState == null) {
Fragment f = EventDetailsFragment.newInstance(event);
getSupportFragmentManager().beginTransaction().add(R.id.content, f).commit();
@ -54,11 +57,14 @@ public class EventDetailsActivity extends ActionBarActivity implements LoaderCal
}
/**
* Initialize event-related ActionBar configuration after the event has been loaded.
* Initialize event-related configuration after the event has been loaded.
*/
private void initActionBar() {
private void initEvent(Event event) {
this.event = event;
// Enable up navigation only after getting the event details
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Enable Android Beam
NfcUtils.setAppDataPushMessageCallbackIfAvailable(this, this);
}
@Override
@ -83,6 +89,11 @@ public class EventDetailsActivity extends ActionBarActivity implements LoaderCal
return false;
}
@Override
public byte[] createNfcAppData() {
return String.valueOf(event.getId()).getBytes();
}
private static class EventLoader extends LocalCacheLoader<Event> {
private final long eventId;
@ -100,23 +111,32 @@ public class EventDetailsActivity extends ActionBarActivity implements LoaderCal
@Override
public Loader<Event> onCreateLoader(int id, Bundle args) {
return new EventLoader(this, Long.parseLong(getIntent().getDataString()));
Intent intent = getIntent();
String eventIdString;
if (NfcUtils.hasAppData(intent)) {
// NFC intent
eventIdString = new String(NfcUtils.extractAppData(intent));
} else {
// Normal in-app intent
eventIdString = intent.getDataString();
}
return new EventLoader(this, Long.parseLong(eventIdString));
}
@Override
public void onLoadFinished(Loader<Event> loader, Event data) {
if (data == null) {
// Event not found, quit
Toast.makeText(this, getString(R.string.event_not_found_error), Toast.LENGTH_LONG).show();
finish();
return;
}
event = data;
initActionBar();
initEvent(data);
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentById(R.id.content) == null) {
Fragment f = EventDetailsFragment.newInstance(event);
Fragment f = EventDetailsFragment.newInstance(data);
fm.beginTransaction().add(R.id.content, f).commitAllowingStateLoss();
}
}

View file

@ -16,6 +16,8 @@ import be.digitalia.fosdem.fragments.TrackScheduleListFragment;
import be.digitalia.fosdem.model.Day;
import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.model.Track;
import be.digitalia.fosdem.utils.NfcUtils;
import be.digitalia.fosdem.utils.NfcUtils.CreateNfcAppDataCallback;
/**
* Track Schedule container, works in both single pane and dual pane modes.
@ -23,7 +25,7 @@ import be.digitalia.fosdem.model.Track;
* @author Christophe Beyls
*
*/
public class TrackScheduleActivity extends ActionBarActivity implements TrackScheduleListFragment.Callbacks {
public class TrackScheduleActivity extends ActionBarActivity implements TrackScheduleListFragment.Callbacks, CreateNfcAppDataCallback {
public static final String EXTRA_DAY = "day";
public static final String EXTRA_TRACK = "track";
@ -33,6 +35,7 @@ public class TrackScheduleActivity extends ActionBarActivity implements TrackSch
private Day day;
private Track track;
private boolean isTabletLandscape;
private Event lastSelectedEvent;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -72,6 +75,19 @@ public class TrackScheduleActivity extends ActionBarActivity implements TrackSch
}
}
trackScheduleListFragment.setSelectionEnabled(isTabletLandscape);
if (isTabletLandscape) {
// Enable Android Beam
NfcUtils.setAppDataPushMessageCallbackIfAvailable(this, this);
}
}
@Override
public byte[] createNfcAppData() {
if (lastSelectedEvent == null) {
return null;
}
return String.valueOf(lastSelectedEvent.getId()).getBytes();
}
@Override
@ -88,6 +104,8 @@ public class TrackScheduleActivity extends ActionBarActivity implements TrackSch
public void onEventSelected(int position, Event event) {
if (isTabletLandscape) {
// Tablet mode: Show event details in the right pane fragment
lastSelectedEvent = event;
FragmentManager fm = getSupportFragmentManager();
EventDetailsFragment currentFragment = (EventDetailsFragment) fm.findFragmentById(R.id.event);
if (event != null) {

View file

@ -2,6 +2,7 @@ package be.digitalia.fosdem.activities;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
@ -18,6 +19,8 @@ import be.digitalia.fosdem.fragments.EventDetailsFragment;
import be.digitalia.fosdem.loaders.TrackScheduleLoader;
import be.digitalia.fosdem.model.Day;
import be.digitalia.fosdem.model.Track;
import be.digitalia.fosdem.utils.NfcUtils;
import be.digitalia.fosdem.utils.NfcUtils.CreateNfcAppDataCallback;
import com.viewpagerindicator.PageIndicator;
@ -27,7 +30,7 @@ import com.viewpagerindicator.PageIndicator;
* @author Christophe Beyls
*
*/
public class TrackScheduleEventActivity extends ActionBarActivity implements LoaderCallbacks<Cursor> {
public class TrackScheduleEventActivity extends ActionBarActivity implements LoaderCallbacks<Cursor>, CreateNfcAppDataCallback {
public static final String EXTRA_DAY = "day";
public static final String EXTRA_TRACK = "track";
@ -69,6 +72,9 @@ public class TrackScheduleEventActivity extends ActionBarActivity implements Loa
bar.setTitle(R.string.event_details);
bar.setSubtitle(track.getName());
// Enable Android Beam
NfcUtils.setAppDataPushMessageCallbackIfAvailable(this, this);
setCustomProgressVisibility(true);
getSupportLoaderManager().initLoader(EVENTS_LOADER_ID, null, this);
}
@ -77,6 +83,18 @@ public class TrackScheduleEventActivity extends ActionBarActivity implements Loa
progress.setVisibility(isVisible ? View.VISIBLE : View.GONE);
}
@Override
public byte[] createNfcAppData() {
if (adapter.getCount() == 0) {
return null;
}
long eventId = adapter.getItemId(pager.getCurrentItem());
if (eventId == -1L) {
return null;
}
return String.valueOf(eventId).getBytes();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@ -145,5 +163,12 @@ public class TrackScheduleEventActivity extends ActionBarActivity implements Loa
cursor.moveToPosition(position);
return EventDetailsFragment.newInstance(DatabaseManager.toEvent(cursor));
}
public long getItemId(int position) {
if (!cursor.moveToPosition(position)) {
return -1L;
}
return cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
}
}
}

View file

@ -0,0 +1,28 @@
package be.digitalia.fosdem.utils;
import android.annotation.TargetApi;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Parcelable;
/**
* NFC helper methods for receiving data sent by NfcSenderUtils. This class wraps API 10+ code.
*
* @author Christophe Beyls
*
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
class NfcReceiverUtils {
public static boolean hasAppData(Intent intent) {
return NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction());
}
public static byte[] extractAppData(Intent intent) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage msg = (NdefMessage) rawMsgs[0];
return msg.getRecords()[0].getPayload();
}
}

View file

@ -0,0 +1,51 @@
package be.digitalia.fosdem.utils;
import java.nio.charset.Charset;
import be.digitalia.fosdem.utils.NfcUtils.CreateNfcAppDataCallback;
import android.annotation.TargetApi;
import android.app.Activity;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcEvent;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.os.Build;
/**
* NFC helper methods for Android Beam foreground push. This class wraps API 14+ code.
*
* @author Christophe Beyls
*
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
class NfcSenderUtils {
public static boolean setAppDataPushMessageCallbackIfAvailable(Activity activity, final CreateNfcAppDataCallback callback) {
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
if (adapter == null) {
return false;
}
final String packageName = activity.getPackageName();
adapter.setNdefPushMessageCallback(new CreateNdefMessageCallback() {
@Override
public NdefMessage createNdefMessage(NfcEvent event) {
byte[] appData = callback.createNfcAppData();
if (appData == null) {
return null;
}
NdefRecord[] records = new NdefRecord[] { createMimeRecord("application/" + packageName, appData),
NdefRecord.createApplicationRecord(packageName) };
return new NdefMessage(records);
}
}, activity);
return true;
}
private static NdefRecord createMimeRecord(String mimeType, byte[] payload) {
byte[] mimeBytes = mimeType.getBytes(Charset.forName("US-ASCII"));
return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, mimeBytes, new byte[0], payload);
}
}

View file

@ -0,0 +1,67 @@
package be.digitalia.fosdem.utils;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
/**
* NFC helper methods compatible with all API levels.
*
* @author Christophe Beyls
*
*/
public class NfcUtils {
/**
* Implement this interface to create application-specific data to be shared through Android Beam.
*/
public interface CreateNfcAppDataCallback {
/**
*
* @return The app data, or null if no data is currently available for sharing.
*/
public byte[] createNfcAppData();
}
/**
* Call this method in an Activity, between onCreate() and onDestroy(), to make its content sharable using Android Beam if available. MIME type of the data
* to share will be "application/" followed by the app's package name. Declare it in your Manifest's intent filters as the data type with an action of
* android.nfc.action.NDEF_DISCOVERED to handle the NFC Intents on the receiver side.
*
* @param activity
* @param callback
* @return true if NFC is available and the content was made available, false if not.
*/
public static boolean setAppDataPushMessageCallbackIfAvailable(Activity activity, final CreateNfcAppDataCallback callback) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return NfcSenderUtils.setAppDataPushMessageCallbackIfAvailable(activity, callback);
}
return false;
}
/**
* Determines if the intent contains NFC NDEF application-specific data to be extracted.
*
* @param intent
* @return
*/
public static boolean hasAppData(Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1) {
return NfcReceiverUtils.hasAppData(intent);
}
return false;
}
/**
* Extracts application-specific data sent through NFC from an intent. You must first ensure that the intent contains NFC data by calling hasAppData().
*
* @param intent
* @return The extracted data
*/
public static byte[] extractAppData(Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1) {
return NfcReceiverUtils.extractAppData(intent);
}
return null;
}
}