Adding new samples to browseable section of DAC

Change-Id: I58e10e787f5df668331fc04e97a6c2efcd75f76f
This commit is contained in:
Alexander Lucas
2014-02-06 15:38:51 -08:00
parent 01d72b37a8
commit 0b3758ea4e
399 changed files with 16010 additions and 369 deletions

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.cardemulation;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
/**
* Utility class for persisting account numbers to disk.
*
* <p>The default SharedPreferences instance is used as the backing storage. Values are cached
* in memory for performance.
*
* <p>This class is thread-safe.
*/
public class AccountStorage {
private static final String PREF_ACCOUNT_NUMBER = "account_number";
private static final String DEFAULT_ACCOUNT_NUMBER = "00000000";
private static final String TAG = "AccountStorage";
private static String sAccount = null;
private static final Object sAccountLock = new Object();
public static void SetAccount(Context c, String s) {
synchronized(sAccountLock) {
Log.i(TAG, "Setting account number: " + s);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
prefs.edit().putString(PREF_ACCOUNT_NUMBER, s).commit();
sAccount = s;
}
}
public static String GetAccount(Context c) {
synchronized (sAccountLock) {
if (sAccount == null) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c);
String account = prefs.getString(PREF_ACCOUNT_NUMBER, DEFAULT_ACCOUNT_NUMBER);
sAccount = account;
}
return sAccount;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.cardemulation;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
/**
* Generic UI for sample discovery.
*/
public class CardEmulationFragment extends Fragment {
public static final String TAG = "CardEmulationFragment";
/** Called when sample is created. Displays generic UI with welcome text. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.main_fragment, container, false);
EditText account = (EditText) v.findViewById(R.id.card_account_field);
account.setText(AccountStorage.GetAccount(getActivity()));
account.addTextChangedListener(new AccountUpdater());
return v;
}
private class AccountUpdater implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Not implemented.
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Not implemented.
}
@Override
public void afterTextChanged(Editable s) {
String account = s.toString();
AccountStorage.SetAccount(getActivity(), account);
}
}
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.cardemulation;
import android.nfc.cardemulation.HostApduService;
import android.os.Bundle;
import com.example.android.common.logger.Log;
import java.util.Arrays;
/**
* This is a sample APDU Service which demonstrates how to interface with the card emulation support
* added in Android 4.4, KitKat.
*
* <p>This sample replies to any requests sent with the string "Hello World". In real-world
* situations, you would need to modify this code to implement your desired communication
* protocol.
*
* <p>This sample will be invoked for any terminals selecting AIDs of 0xF11111111, 0xF22222222, or
* 0xF33333333. See src/main/res/xml/aid_list.xml for more details.
*
* <p class="note">Note: This is a low-level interface. Unlike the NdefMessage many developers
* are familiar with for implementing Android Beam in apps, card emulation only provides a
* byte-array based communication channel. It is left to developers to implement higher level
* protocol support as needed.
*/
public class CardService extends HostApduService {
private static final String TAG = "CardService";
// AID for our loyalty card service.
private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222";
// ISO-DEP command HEADER for selecting an AID.
// Format: [Class | Instruction | Parameter 1 | Parameter 2]
private static final String SELECT_APDU_HEADER = "00A40400";
// "OK" status word sent in response to SELECT AID command (0x9000)
private static final byte[] SELECT_OK_SW = HexStringToByteArray("9000");
// "UNKNOWN" status word sent in response to invalid APDU command (0x0000)
private static final byte[] UNKNOWN_CMD_SW = HexStringToByteArray("0000");
private static final byte[] SELECT_APDU = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID);
/**
* Called if the connection to the NFC card is lost, in order to let the application know the
* cause for the disconnection (either a lost link, or another AID being selected by the
* reader).
*
* @param reason Either DEACTIVATION_LINK_LOSS or DEACTIVATION_DESELECTED
*/
@Override
public void onDeactivated(int reason) { }
/**
* This method will be called when a command APDU has been received from a remote device. A
* response APDU can be provided directly by returning a byte-array in this method. In general
* response APDUs must be sent as quickly as possible, given the fact that the user is likely
* holding his device over an NFC reader when this method is called.
*
* <p class="note">If there are multiple services that have registered for the same AIDs in
* their meta-data entry, you will only get called if the user has explicitly selected your
* service, either as a default or just for the next tap.
*
* <p class="note">This method is running on the main thread of your application. If you
* cannot return a response APDU immediately, return null and use the {@link
* #sendResponseApdu(byte[])} method later.
*
* @param commandApdu The APDU that received from the remote device
* @param extras A bundle containing extra data. May be null.
* @return a byte-array containing the response APDU, or null if no response APDU can be sent
* at this point.
*/
// BEGIN_INCLUDE(processCommandApdu)
@Override
public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
Log.i(TAG, "Received APDU: " + ByteArrayToHexString(commandApdu));
// If the APDU matches the SELECT AID command for this service,
// send the loyalty card account number, followed by a SELECT_OK status trailer (0x9000).
if (Arrays.equals(SELECT_APDU, commandApdu)) {
String account = AccountStorage.GetAccount(this);
byte[] accountBytes = account.getBytes();
Log.i(TAG, "Sending account number: " + account);
return ConcatArrays(accountBytes, SELECT_OK_SW);
} else {
return UNKNOWN_CMD_SW;
}
}
// END_INCLUDE(processCommandApdu)
/**
* Build APDU for SELECT AID command. This command indicates which service a reader is
* interested in communicating with. See ISO 7816-4.
*
* @param aid Application ID (AID) to select
* @return APDU for SELECT AID command
*/
public static byte[] BuildSelectApdu(String aid) {
// Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA]
return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X",
aid.length() / 2) + aid);
}
/**
* Utility method to convert a byte array to a hexadecimal string.
*
* @param bytes Bytes to convert
* @return String, containing hexadecimal representation.
*/
public static String ByteArrayToHexString(byte[] bytes) {
final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[bytes.length * 2]; // Each byte has two hex characters (nibbles)
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF; // Cast bytes[j] to int, treating as unsigned value
hexChars[j * 2] = hexArray[v >>> 4]; // Select hex character from upper nibble
hexChars[j * 2 + 1] = hexArray[v & 0x0F]; // Select hex character from lower nibble
}
return new String(hexChars);
}
/**
* Utility method to convert a hexadecimal string to a byte string.
*
* <p>Behavior with input strings containing non-hexadecimal characters is undefined.
*
* @param s String containing hexadecimal characters to convert
* @return Byte array generated from input
* @throws java.lang.IllegalArgumentException if input length is incorrect
*/
public static byte[] HexStringToByteArray(String s) throws IllegalArgumentException {
int len = s.length();
if (len % 2 == 1) {
throw new IllegalArgumentException("Hex string must have even number of characters");
}
byte[] data = new byte[len / 2]; // Allocate 1 byte per 2 hex characters
for (int i = 0; i < len; i += 2) {
// Convert each character into a integer (base-16), then bit-shift into place
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
/**
* Utility method to concatenate two byte arrays.
* @param first First array
* @param rest Any remaining arrays
* @return Concatenated copy of input arrays
*/
public static byte[] ConcatArrays(byte[] first, byte[]... rest) {
int totalLength = first.length;
for (byte[] array : rest) {
totalLength += array.length;
}
byte[] result = Arrays.copyOf(first, totalLength);
int offset = first.length;
for (byte[] array : rest) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.cardemulation;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ViewAnimator;
import com.example.android.common.activities.SampleActivityBase;
import com.example.android.common.logger.Log;
import com.example.android.common.logger.LogFragment;
import com.example.android.common.logger.LogWrapper;
import com.example.android.common.logger.MessageOnlyLogFilter;
/**
* A simple launcher activity containing a summary sample description, sample log and a custom
* {@link android.support.v4.app.Fragment} which can display a view.
* <p>
* For devices with displays with a width of 720dp or greater, the sample log is always visible,
* on other devices it's visibility is controlled by an item on the Action Bar.
*/
public class MainActivity extends SampleActivityBase {
public static final String TAG = "MainActivity";
// Whether the Log Fragment is currently shown
private boolean mLogShown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
CardEmulationFragment fragment = new CardEmulationFragment();
transaction.replace(R.id.sample_content_fragment, fragment);
transaction.commit();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem logToggle = menu.findItem(R.id.menu_toggle_log);
logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator);
logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_toggle_log:
mLogShown = !mLogShown;
ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output);
if (mLogShown) {
output.setDisplayedChild(1);
} else {
output.setDisplayedChild(0);
}
supportInvalidateOptionsMenu();
return true;
}
return super.onOptionsItemSelected(item);
}
/** Create a chain of targets that will receive log data */
@Override
public void initializeLogging() {
// Wraps Android's native log framework.
LogWrapper logWrapper = new LogWrapper();
// Using Log, front-end to the logging chain, emulates android.util.log method signatures.
Log.setLogNode(logWrapper);
// Filter strips out everything except the message text.
MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter();
logWrapper.setNext(msgFilter);
// On screen logging via a fragment with a TextView.
LogFragment logFragment = (LogFragment) getSupportFragmentManager()
.findFragmentById(R.id.log_fragment);
msgFilter.setNext(logFragment.getLogView());
Log.i(TAG, "Ready");
}
}