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" /> android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<!-- Permissions required for alarms --> <!-- Permissions required for alarms -->
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
@ -46,7 +47,15 @@
android:value=".activities.MainActivity" /> android:value=".activities.MainActivity" />
</activity> </activity>
<activity android:name=".activities.TrackScheduleEventActivity" /> <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.PersonInfoActivity" />
<activity <activity
android:name=".activities.SearchResultActivity" android:name=".activities.SearchResultActivity"

View file

@ -51,6 +51,7 @@
<!-- Errors --> <!-- Errors -->
<string name="error_title">Error</string> <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="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> <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.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast;
import be.digitalia.fosdem.R; import be.digitalia.fosdem.R;
import be.digitalia.fosdem.db.DatabaseManager; import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.fragments.EventDetailsFragment; import be.digitalia.fosdem.fragments.EventDetailsFragment;
import be.digitalia.fosdem.loaders.LocalCacheLoader; import be.digitalia.fosdem.loaders.LocalCacheLoader;
import be.digitalia.fosdem.model.Event; 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. * 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 * @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"; public static final String EXTRA_EVENT = "event";
@ -38,11 +41,11 @@ public class EventDetailsActivity extends ActionBarActivity implements LoaderCal
getSupportActionBar().setTitle(R.string.event_details); getSupportActionBar().setTitle(R.string.event_details);
event = getIntent().getParcelableExtra(EXTRA_EVENT); Event event = getIntent().getParcelableExtra(EXTRA_EVENT);
if (event != null) { if (event != null) {
// The event has been passed as parameter, it can be displayed immediately // The event has been passed as parameter, it can be displayed immediately
initActionBar(); initEvent(event);
if (savedInstanceState == null) { if (savedInstanceState == null) {
Fragment f = EventDetailsFragment.newInstance(event); Fragment f = EventDetailsFragment.newInstance(event);
getSupportFragmentManager().beginTransaction().add(R.id.content, f).commit(); 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 // Enable up navigation only after getting the event details
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Enable Android Beam
NfcUtils.setAppDataPushMessageCallbackIfAvailable(this, this);
} }
@Override @Override
@ -83,6 +89,11 @@ public class EventDetailsActivity extends ActionBarActivity implements LoaderCal
return false; return false;
} }
@Override
public byte[] createNfcAppData() {
return String.valueOf(event.getId()).getBytes();
}
private static class EventLoader extends LocalCacheLoader<Event> { private static class EventLoader extends LocalCacheLoader<Event> {
private final long eventId; private final long eventId;
@ -100,23 +111,32 @@ public class EventDetailsActivity extends ActionBarActivity implements LoaderCal
@Override @Override
public Loader<Event> onCreateLoader(int id, Bundle args) { 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 @Override
public void onLoadFinished(Loader<Event> loader, Event data) { public void onLoadFinished(Loader<Event> loader, Event data) {
if (data == null) { if (data == null) {
// Event not found, quit // Event not found, quit
Toast.makeText(this, getString(R.string.event_not_found_error), Toast.LENGTH_LONG).show();
finish(); finish();
return; return;
} }
event = data; initEvent(data);
initActionBar();
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentById(R.id.content) == null) { 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(); 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.Day;
import be.digitalia.fosdem.model.Event; import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.model.Track; 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. * 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 * @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_DAY = "day";
public static final String EXTRA_TRACK = "track"; public static final String EXTRA_TRACK = "track";
@ -33,6 +35,7 @@ public class TrackScheduleActivity extends ActionBarActivity implements TrackSch
private Day day; private Day day;
private Track track; private Track track;
private boolean isTabletLandscape; private boolean isTabletLandscape;
private Event lastSelectedEvent;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -72,6 +75,19 @@ public class TrackScheduleActivity extends ActionBarActivity implements TrackSch
} }
} }
trackScheduleListFragment.setSelectionEnabled(isTabletLandscape); 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 @Override
@ -88,6 +104,8 @@ public class TrackScheduleActivity extends ActionBarActivity implements TrackSch
public void onEventSelected(int position, Event event) { public void onEventSelected(int position, Event event) {
if (isTabletLandscape) { if (isTabletLandscape) {
// Tablet mode: Show event details in the right pane fragment // Tablet mode: Show event details in the right pane fragment
lastSelectedEvent = event;
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
EventDetailsFragment currentFragment = (EventDetailsFragment) fm.findFragmentById(R.id.event); EventDetailsFragment currentFragment = (EventDetailsFragment) fm.findFragmentById(R.id.event);
if (event != null) { if (event != null) {

View file

@ -2,6 +2,7 @@ package be.digitalia.fosdem.activities;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.provider.BaseColumns;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter; 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.loaders.TrackScheduleLoader;
import be.digitalia.fosdem.model.Day; import be.digitalia.fosdem.model.Day;
import be.digitalia.fosdem.model.Track; import be.digitalia.fosdem.model.Track;
import be.digitalia.fosdem.utils.NfcUtils;
import be.digitalia.fosdem.utils.NfcUtils.CreateNfcAppDataCallback;
import com.viewpagerindicator.PageIndicator; import com.viewpagerindicator.PageIndicator;
@ -27,7 +30,7 @@ import com.viewpagerindicator.PageIndicator;
* @author Christophe Beyls * @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_DAY = "day";
public static final String EXTRA_TRACK = "track"; 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.setTitle(R.string.event_details);
bar.setSubtitle(track.getName()); bar.setSubtitle(track.getName());
// Enable Android Beam
NfcUtils.setAppDataPushMessageCallbackIfAvailable(this, this);
setCustomProgressVisibility(true); setCustomProgressVisibility(true);
getSupportLoaderManager().initLoader(EVENTS_LOADER_ID, null, this); 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); 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 @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
@ -145,5 +163,12 @@ public class TrackScheduleEventActivity extends ActionBarActivity implements Loa
cursor.moveToPosition(position); cursor.moveToPosition(position);
return EventDetailsFragment.newInstance(DatabaseManager.toEvent(cursor)); 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;
}
}