├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── example │ │ └── android │ │ └── proximitymanager │ │ ├── AttachmentCreateActivity.java │ │ ├── AttachmentsListActivity.java │ │ ├── AuthenticationActivity.java │ │ ├── BeaconListActivity.java │ │ ├── BeaconRegisterActivity.java │ │ ├── Utils.java │ │ ├── api │ │ ├── ApiDataCallback.java │ │ ├── AttachmentsLoader.java │ │ ├── AuthenticatedRequest.java │ │ ├── BaseApiLoader.java │ │ ├── BeaconsLoader.java │ │ ├── NamespacesLoader.java │ │ └── ProximityApi.java │ │ └── data │ │ ├── Attachment.java │ │ ├── AttachmentAdapter.java │ │ ├── Beacon.java │ │ ├── BeaconAdapter.java │ │ └── Namespace.java │ └── res │ ├── layout │ ├── activity_auth.xml │ ├── activity_create.xml │ ├── activity_list.xml │ ├── attachment_item.xml │ ├── beacon_item.xml │ └── register_dialog.xml │ ├── menu │ ├── menu_attachment_list.xml │ └── menu_beacon_list.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── volley ├── .gitignore ├── Android.mk ├── build.gradle ├── build.xml ├── custom_rules.xml ├── pom.xml ├── proguard-project.txt ├── proguard.cfg ├── rules.gradle └── src ├── main ├── AndroidManifest.xml └── java │ └── com │ └── android │ └── volley │ ├── AuthFailureError.java │ ├── Cache.java │ ├── CacheDispatcher.java │ ├── DefaultRetryPolicy.java │ ├── ExecutorDelivery.java │ ├── Network.java │ ├── NetworkDispatcher.java │ ├── NetworkError.java │ ├── NetworkResponse.java │ ├── NoConnectionError.java │ ├── ParseError.java │ ├── Request.java │ ├── RequestQueue.java │ ├── Response.java │ ├── ResponseDelivery.java │ ├── RetryPolicy.java │ ├── ServerError.java │ ├── TimeoutError.java │ ├── VolleyError.java │ ├── VolleyLog.java │ └── toolbox │ ├── AndroidAuthenticator.java │ ├── Authenticator.java │ ├── BasicNetwork.java │ ├── ByteArrayPool.java │ ├── ClearCacheRequest.java │ ├── DiskBasedCache.java │ ├── HttpClientStack.java │ ├── HttpHeaderParser.java │ ├── HttpStack.java │ ├── HurlStack.java │ ├── ImageLoader.java │ ├── ImageRequest.java │ ├── JsonArrayRequest.java │ ├── JsonObjectRequest.java │ ├── JsonRequest.java │ ├── NetworkImageView.java │ ├── NoCache.java │ ├── PoolingByteArrayOutputStream.java │ ├── RequestFuture.java │ ├── StringRequest.java │ └── Volley.java └── test ├── java └── com │ └── android │ └── volley │ ├── CacheDispatcherTest.java │ ├── NetworkDispatcherTest.java │ ├── RequestQueueIntegrationTest.java │ ├── RequestQueueTest.java │ ├── RequestTest.java │ ├── ResponseDeliveryTest.java │ ├── mock │ ├── MockCache.java │ ├── MockHttpClient.java │ ├── MockHttpStack.java │ ├── MockHttpURLConnection.java │ ├── MockNetwork.java │ ├── MockRequest.java │ ├── MockResponseDelivery.java │ ├── ShadowSystemClock.java │ ├── TestRequest.java │ └── WaitableQueue.java │ ├── toolbox │ ├── AndroidAuthenticatorTest.java │ ├── BasicNetworkTest.java │ ├── ByteArrayPoolTest.java │ ├── CacheTest.java │ ├── DiskBasedCacheTest.java │ ├── HttpClientStackTest.java │ ├── HttpHeaderParserTest.java │ ├── HurlStackTest.java │ ├── ImageLoaderTest.java │ ├── ImageRequestTest.java │ ├── JsonRequestCharsetTest.java │ ├── JsonRequestTest.java │ ├── NetworkImageViewTest.java │ ├── PoolingByteArrayOutputStreamTest.java │ ├── RequestFutureTest.java │ ├── RequestQueueTest.java │ ├── RequestTest.java │ ├── ResponseTest.java │ └── StringRequestTest.java │ └── utils │ ├── CacheTestUtils.java │ └── ImmediateResponseDelivery.java └── resources └── org.robolectric.Config.properties /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/ 4 | *.iml 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Wireless Designs, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proximity Beacon Manager 2 | 3 | This repository contains a sample beacon manager application on Android using Google's [Proximity Beacon API](https://developers.google.com/beacons/proximity/guides). The application exposes a few basic functions of the API: 4 | 5 | * Registering new beacons 6 | * Listing registered beacons 7 | * Listing beacon attachments 8 | * Listing project namespaces 9 | * Creating & deleting beacon attachments 10 | 11 | # Disclaimer 12 | 13 | This repository contains sample code intended to demonstrate the capabilities of the Proximity Beacon API. It is not intended to be used as-is in applications as a library dependency—or a stand-alone production application—and will not be maintained as such. Bug fix contributions are welcome, but issues and feature requests will not be addressed. 14 | 15 | # Sample Usage 16 | 17 | The beacon API (and this sample application) uses your Google account and OAuth 2.0 to authenticate all requests. Because of this, you do not need to make any source code changes to use it. You must simply choose a Google account that is already attached to your developer's console project where the Proximity Beacon API is enabled. 18 | 19 | For more information on enabling the API and creating the proper API credentials, follow the beacon API [Getting Started Guide](https://developers.google.com/beacons/proximity/get-started). 20 | 21 | ## Discovering Beacons 22 | 23 | On your first run through the application (after selecting an account) there will likely be no beacons to list. Select _Register Beacon_ from the options menu to launch the beacon scanner. This activity will scan for advertising _Eddystone-UID_ beacons and allow you to register them with your console project. 24 | 25 | ## Managing Beacons 26 | 27 | Once you have one or more beacons registered, you can tap on those beacons in the main list to access their attachments. You may create new attachments using the _add_ action in the options menu. Existing attachments can also be deleted from the attachments list. 28 | 29 | To assist in making new attachments easier, tapping on an existing attachment will copy its contents to the clipboard so they can be pasted into the _create_ activity. 30 | 31 | # License 32 | 33 | The code supplied here is covered under the MIT Open Source License: 34 | 35 | Copyright (c) 2015 Wireless Designs, LLC 36 | 37 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.0" 6 | 7 | defaultConfig { 8 | applicationId "com.example.android.proximitymanager" 9 | minSdkVersion 21 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:23.0.0' 25 | compile 'com.google.android.gms:play-services:7.8.0' 26 | compile project(':volley') 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/davesmith/android-sdk-mac_86/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/AttachmentCreateActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v4.app.LoaderManager; 6 | import android.support.v4.content.Loader; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.view.View; 9 | import android.widget.ArrayAdapter; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.Spinner; 13 | 14 | import com.example.android.proximitymanager.api.ApiDataCallback; 15 | import com.example.android.proximitymanager.api.NamespacesLoader; 16 | import com.example.android.proximitymanager.api.ProximityApi; 17 | import com.example.android.proximitymanager.data.Beacon; 18 | import com.example.android.proximitymanager.data.Namespace; 19 | 20 | import java.util.List; 21 | 22 | public class AttachmentCreateActivity extends AppCompatActivity implements 23 | LoaderManager.LoaderCallbacks>, 24 | View.OnClickListener, 25 | ApiDataCallback { 26 | 27 | private Spinner mNamespaceSelect; 28 | private EditText mAttachmentType, mAttachmentText; 29 | private Button mSaveButton; 30 | private ArrayAdapter mNamespaceAdapter; 31 | 32 | private ProximityApi mProximityApi; 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_create); 38 | 39 | mAttachmentType = (EditText) findViewById(R.id.text_type); 40 | mAttachmentText = (EditText) findViewById(R.id.text_attach); 41 | mSaveButton = (Button) findViewById(R.id.button_save); 42 | 43 | //Default button to disabled 44 | mSaveButton.setOnClickListener(this); 45 | mSaveButton.setEnabled(false); 46 | 47 | mNamespaceSelect = (Spinner) findViewById(R.id.spinner); 48 | 49 | mNamespaceAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item); 50 | mNamespaceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 51 | mNamespaceSelect.setAdapter(mNamespaceAdapter); 52 | 53 | mProximityApi = ProximityApi.getInstance(this); 54 | 55 | getSupportLoaderManager().initLoader(0, null, this); 56 | } 57 | 58 | @Override 59 | public void onClick(View v) { 60 | final String beaconName = getIntent().getStringExtra(Intent.EXTRA_UID); 61 | 62 | Namespace selectedNamespace = mNamespaceAdapter.getItem( 63 | mNamespaceSelect.getSelectedItemPosition()); 64 | String typeString = mAttachmentType.getText().toString(); 65 | final String namespacedType = selectedNamespace.getNamespacedType(typeString); 66 | 67 | final String data = mAttachmentText.getText().toString(); 68 | 69 | mProximityApi.createAttachment(beaconName, data, namespacedType); 70 | finish(); 71 | } 72 | 73 | private void updateSaveButton() { 74 | mSaveButton.setEnabled(!mNamespaceAdapter.isEmpty()); 75 | } 76 | 77 | /* LoaderCallbacks Methods */ 78 | 79 | @Override 80 | public Loader> onCreateLoader(int id, Bundle args) { 81 | return new NamespacesLoader(this); 82 | } 83 | 84 | @Override 85 | public void onLoadFinished(Loader> loader, List data) { 86 | mNamespaceAdapter.setNotifyOnChange(false); 87 | mNamespaceAdapter.clear(); 88 | mNamespaceAdapter.addAll(data); 89 | mNamespaceAdapter.notifyDataSetChanged(); 90 | updateSaveButton(); 91 | } 92 | 93 | @Override 94 | public void onLoaderReset(Loader> loader) { 95 | mNamespaceAdapter.clear(); 96 | updateSaveButton(); 97 | } 98 | 99 | /* ApiDataCallback Methods */ 100 | 101 | @Override 102 | public void onBeaconResponse(Beacon beacon) { 103 | //Not used in this context 104 | } 105 | 106 | @Override 107 | public void onAttachmentResponse() { 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/AttachmentsListActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.ClipData; 5 | import android.content.ClipboardManager; 6 | import android.content.DialogInterface; 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.support.v4.app.LoaderManager; 10 | import android.support.v4.content.Loader; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.widget.AdapterView; 16 | import android.widget.ListView; 17 | 18 | import com.example.android.proximitymanager.api.ApiDataCallback; 19 | import com.example.android.proximitymanager.api.AttachmentsLoader; 20 | import com.example.android.proximitymanager.api.ProximityApi; 21 | import com.example.android.proximitymanager.data.Attachment; 22 | import com.example.android.proximitymanager.data.AttachmentAdapter; 23 | import com.example.android.proximitymanager.data.Beacon; 24 | 25 | import java.util.List; 26 | 27 | 28 | public class AttachmentsListActivity extends AppCompatActivity implements 29 | LoaderManager.LoaderCallbacks>, 30 | AdapterView.OnItemClickListener, 31 | ApiDataCallback { 32 | 33 | private ListView mList; 34 | private AttachmentAdapter mAdapter; 35 | 36 | private ProximityApi mProximityApi; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_list); 42 | 43 | mList = (ListView) findViewById(R.id.list); 44 | 45 | mAdapter = new AttachmentAdapter(this); 46 | mList.setAdapter(mAdapter); 47 | mList.setOnItemClickListener(this); 48 | 49 | mProximityApi = ProximityApi.getInstance(this); 50 | 51 | getSupportLoaderManager().initLoader(0, getIntent().getExtras(), this); 52 | } 53 | 54 | @Override 55 | protected void onResume() { 56 | super.onResume(); 57 | refreshLoader(); 58 | mProximityApi.registerDataCallback(this); 59 | } 60 | 61 | @Override 62 | protected void onPause() { 63 | super.onPause(); 64 | mProximityApi.unregisterDataCallback(this); 65 | } 66 | 67 | @Override 68 | public boolean onCreateOptionsMenu(Menu menu) { 69 | getMenuInflater().inflate(R.menu.menu_attachment_list, menu); 70 | return true; 71 | } 72 | 73 | @Override 74 | public boolean onOptionsItemSelected(MenuItem item) { 75 | switch (item.getItemId()) { 76 | case R.id.action_refresh: 77 | refreshLoader(); 78 | return true; 79 | case R.id.action_add: 80 | Intent intent = new Intent(this, AttachmentCreateActivity.class); 81 | intent.putExtras(getIntent().getExtras()); 82 | startActivity(intent); 83 | return true; 84 | default: 85 | return super.onOptionsItemSelected(item); 86 | } 87 | } 88 | 89 | @Override 90 | public void onItemClick(AdapterView parent, View view, int position, long id) { 91 | final Attachment item = mAdapter.getItem(position); 92 | new AlertDialog.Builder(this) 93 | .setTitle("Copy Data?") 94 | .setMessage("Do you want to copy this attachments data to the clipboard?") 95 | .setNegativeButton("Cancel", null) 96 | .setPositiveButton("Copy", new DialogInterface.OnClickListener() { 97 | @Override 98 | public void onClick(DialogInterface dialog, int which) { 99 | ClipboardManager manager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 100 | manager.setPrimaryClip(ClipData.newPlainText("Attachment Item", item.getData())); 101 | 102 | Utils.showToast(AttachmentsListActivity.this, "Saved to Clipboard"); 103 | } 104 | }) 105 | .show(); 106 | } 107 | 108 | /* LoaderCallbacks Methods */ 109 | 110 | @Override 111 | public Loader> onCreateLoader(int id, Bundle args) { 112 | return new AttachmentsLoader(this, args.getString(Intent.EXTRA_UID)); 113 | } 114 | 115 | @Override 116 | public void onLoadFinished(Loader> loader, List data) { 117 | if (mList.getEmptyView() == null) { 118 | mList.setEmptyView(findViewById(R.id.empty)); 119 | } 120 | 121 | if (data == null) { 122 | Utils.showToast(this, "Unable to load attachments"); 123 | return; 124 | } 125 | 126 | mAdapter.setNotifyOnChange(false); 127 | mAdapter.clear(); 128 | mAdapter.addAll(data); 129 | mAdapter.notifyDataSetChanged(); 130 | } 131 | 132 | @Override 133 | public void onLoaderReset(Loader> loader) { 134 | mAdapter.clear(); 135 | } 136 | 137 | private void refreshLoader() { 138 | getSupportLoaderManager() 139 | .restartLoader(0, getIntent().getExtras(), this); 140 | } 141 | 142 | /* ApiDataCallback Methods */ 143 | 144 | @Override 145 | public void onBeaconResponse(Beacon beacon) { 146 | refreshLoader(); 147 | } 148 | 149 | @Override 150 | public void onAttachmentResponse() { 151 | refreshLoader(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/BeaconListActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v4.app.LoaderManager; 6 | import android.support.v4.content.Loader; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | import android.widget.AdapterView; 12 | import android.widget.ListView; 13 | 14 | import com.example.android.proximitymanager.api.BeaconsLoader; 15 | import com.example.android.proximitymanager.api.ProximityApi; 16 | import com.example.android.proximitymanager.data.Beacon; 17 | import com.example.android.proximitymanager.data.BeaconAdapter; 18 | 19 | import java.util.List; 20 | 21 | public class BeaconListActivity extends AppCompatActivity implements 22 | LoaderManager.LoaderCallbacks>, 23 | AdapterView.OnItemClickListener { 24 | 25 | private static final int REQUEST_AUTH = 42; 26 | 27 | private ListView mList; 28 | private BeaconAdapter mAdapter; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_list); 34 | 35 | mList = (ListView) findViewById(R.id.list); 36 | 37 | mAdapter = new BeaconAdapter(this); 38 | mList.setAdapter(mAdapter); 39 | mList.setOnItemClickListener(this); 40 | 41 | if (!ProximityApi.getInstance(this).hasToken()) { 42 | //Launch account picker for first runs 43 | Intent intent = new Intent(this, AuthenticationActivity.class); 44 | startActivityForResult(intent, REQUEST_AUTH); 45 | } else { 46 | getSupportLoaderManager().initLoader(0, null, this); 47 | } 48 | } 49 | 50 | @Override 51 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 52 | if (requestCode == REQUEST_AUTH) { 53 | if (resultCode != RESULT_OK) { 54 | Utils.showToast(this, 55 | "You must authenticate a valid Google account"); 56 | finish(); 57 | } 58 | } 59 | } 60 | 61 | @Override 62 | protected void onResume() { 63 | super.onResume(); 64 | if (ProximityApi.getInstance(this).hasToken()) { 65 | refreshLoader(); 66 | } 67 | } 68 | 69 | @Override 70 | public boolean onCreateOptionsMenu(Menu menu) { 71 | // Inflate the menu; this adds items to the action bar if it is present. 72 | getMenuInflater().inflate(R.menu.menu_beacon_list, menu); 73 | return true; 74 | } 75 | 76 | @Override 77 | public boolean onOptionsItemSelected(MenuItem item) { 78 | switch (item.getItemId()) { 79 | case R.id.action_refresh: 80 | refreshLoader(); 81 | return true; 82 | case R.id.action_register: 83 | Intent intent = new Intent(this, BeaconRegisterActivity.class); 84 | startActivity(intent); 85 | return true; 86 | default: 87 | return super.onOptionsItemSelected(item); 88 | } 89 | } 90 | 91 | @Override 92 | public void onItemClick(AdapterView parent, View view, int position, long id) { 93 | Beacon beacon = mAdapter.getItem(position); 94 | 95 | Intent intent = new Intent(this, AttachmentsListActivity.class); 96 | intent.putExtra(Intent.EXTRA_UID, beacon.name); 97 | startActivity(intent); 98 | } 99 | 100 | @Override 101 | public Loader> onCreateLoader(int id, Bundle args) { 102 | return new BeaconsLoader(this); 103 | } 104 | 105 | @Override 106 | public void onLoadFinished(Loader> loader, List data) { 107 | if (mList.getEmptyView() == null) { 108 | mList.setEmptyView(findViewById(R.id.empty)); 109 | } 110 | 111 | if (data == null) { 112 | Utils.showToast(this, "Unable to load beacons"); 113 | return; 114 | } 115 | 116 | mAdapter.setNotifyOnChange(false); 117 | mAdapter.clear(); 118 | mAdapter.addAll(data); 119 | mAdapter.notifyDataSetChanged(); 120 | } 121 | 122 | @Override 123 | public void onLoaderReset(Loader> loader) { 124 | mAdapter.clear(); 125 | } 126 | 127 | private void refreshLoader() { 128 | getSupportLoaderManager().restartLoader(0, null, this); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/Utils.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager; 2 | 3 | 4 | import android.content.Context; 5 | import android.widget.Toast; 6 | 7 | public class Utils { 8 | 9 | /** 10 | * Convert section of a byte[] into a hexadecimal string 11 | */ 12 | public static String toHexString(byte[] data, int offset, int length) { 13 | StringBuilder sb = new StringBuilder(); 14 | 15 | for (int i=offset; i < length; i++) { 16 | sb.append(String.format("%02x", data[i] & 0xFF)); 17 | } 18 | 19 | return sb.toString(); 20 | } 21 | 22 | /** 23 | * Show a Toast message 24 | */ 25 | public static void showToast(Context context, CharSequence message) { 26 | Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/api/ApiDataCallback.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.api; 2 | 3 | import com.example.android.proximitymanager.data.Beacon; 4 | 5 | 6 | public interface ApiDataCallback { 7 | void onBeaconResponse(Beacon beacon); 8 | void onAttachmentResponse(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/api/AttachmentsLoader.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.api; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.example.android.proximitymanager.data.Attachment; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Loader to execute and parse beacons.attachments.list 15 | */ 16 | public class AttachmentsLoader extends BaseApiLoader> { 17 | private static final String TAG = AttachmentsLoader.class.getSimpleName(); 18 | 19 | private String mBeaconName; 20 | 21 | public AttachmentsLoader(Context context, String beaconName) { 22 | super(context); 23 | mBeaconName = beaconName; 24 | } 25 | 26 | @Override 27 | protected void onStartRequest() { 28 | ProximityApi.getInstance(getContext()) 29 | .getAttachmentsList(mBeaconName, this, this); 30 | } 31 | 32 | @Override 33 | protected List onHandleResponse(JSONObject response) { 34 | //API Error 35 | if (response == null) return null; 36 | 37 | try { 38 | return Attachment.fromJson(response); 39 | } catch (JSONException e) { 40 | Log.w(TAG, "Parsing error", e); 41 | return null; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/api/AuthenticatedRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.api; 2 | 3 | import com.android.volley.AuthFailureError; 4 | import com.android.volley.Response; 5 | import com.android.volley.toolbox.JsonObjectRequest; 6 | 7 | import org.json.JSONObject; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * Extension of Volley JsonObjectRequest that applies the 14 | * proper request headers for the beacon API client OAuth. 15 | */ 16 | public class AuthenticatedRequest extends JsonObjectRequest { 17 | 18 | private String mAuthToken; 19 | 20 | public AuthenticatedRequest(int method, String url, JSONObject jsonRequest, 21 | Response.Listener listener, 22 | Response.ErrorListener errorListener) { 23 | super(method, url, jsonRequest, listener, errorListener); 24 | } 25 | 26 | public void setAuthToken(String token) { 27 | mAuthToken = token; 28 | } 29 | 30 | public Map getHeaders() throws AuthFailureError { 31 | HashMap headers = new HashMap<>(); 32 | headers.put("Content-Type", "application/json"); 33 | headers.put("Authorization", "Bearer " + mAuthToken); 34 | 35 | return headers; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/api/BaseApiLoader.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.api; 2 | 3 | import android.content.Context; 4 | import android.support.v4.content.Loader; 5 | import android.util.Log; 6 | 7 | import com.android.volley.Response; 8 | import com.android.volley.VolleyError; 9 | 10 | import org.json.JSONObject; 11 | 12 | /** 13 | * Base Loader implementation to provide the hooks to execute Volley 14 | * requests and parse the responses. 15 | */ 16 | public abstract class BaseApiLoader extends Loader implements 17 | Response.Listener, Response.ErrorListener { 18 | T mData; 19 | 20 | public BaseApiLoader(final Context context) { 21 | super(context); 22 | } 23 | 24 | @Override 25 | protected void onForceLoad() { 26 | super.onForceLoad(); 27 | cancelLoad(); 28 | onStartRequest(); 29 | } 30 | 31 | // Override to trigger new API call 32 | protected abstract void onStartRequest(); 33 | 34 | protected abstract T onHandleResponse(JSONObject response); 35 | 36 | @Override 37 | public void deliverResult(final T data) { 38 | mData = data; 39 | 40 | if (isStarted()) { 41 | // If the Loader is currently started, we can immediately 42 | // deliver its results. 43 | super.deliverResult(data); 44 | } 45 | } 46 | 47 | @Override 48 | protected void onStartLoading() { 49 | super.onStartLoading(); 50 | if (mData != null) { 51 | // If we currently have a result available, deliver it 52 | // immediately. 53 | deliverResult(mData); 54 | } 55 | 56 | if (takeContentChanged() || mData == null) { 57 | // If the data has changed since the last time it was loaded 58 | // or is not currently available, start a load. 59 | forceLoad(); 60 | } 61 | } 62 | 63 | @Override 64 | protected void onStopLoading() { 65 | // Attempt to cancel the current load task if possible. 66 | cancelLoad(); 67 | } 68 | 69 | 70 | @Override 71 | protected void onReset() { 72 | super.onReset(); 73 | // Ensure the loader is stopped 74 | onStopLoading(); 75 | // At this point we can release the resources associated with 'data' if needed. 76 | if (mData != null) { 77 | mData = null; 78 | } 79 | } 80 | 81 | /* Volley Listener Methods */ 82 | 83 | @Override 84 | public void onResponse(JSONObject response) { 85 | deliverResult(onHandleResponse(response)); 86 | } 87 | 88 | @Override 89 | public void onErrorResponse(VolleyError error) { 90 | Log.w("ProximityApi", "API Request Error", error); 91 | cancelLoad(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/api/BeaconsLoader.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.api; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.example.android.proximitymanager.data.Beacon; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Loader to execute and parse beacons.list 15 | */ 16 | public class BeaconsLoader extends BaseApiLoader> { 17 | private static final String TAG = BeaconsLoader.class.getSimpleName(); 18 | 19 | public BeaconsLoader(Context context) { 20 | super(context); 21 | } 22 | 23 | @Override 24 | protected void onStartRequest() { 25 | ProximityApi.getInstance(getContext()) 26 | .getBeaconsList(this, this); 27 | } 28 | 29 | @Override 30 | protected List onHandleResponse(JSONObject response) { 31 | //API Error 32 | if (response == null) return null; 33 | 34 | try { 35 | return Beacon.fromJson(response); 36 | } catch (JSONException e) { 37 | Log.w(TAG, "Parsing error", e); 38 | return null; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/api/NamespacesLoader.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.api; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.example.android.proximitymanager.data.Namespace; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Loader to execute and parse namespaces.list 15 | */ 16 | public class NamespacesLoader extends BaseApiLoader> { 17 | private static final String TAG = NamespacesLoader.class.getSimpleName(); 18 | 19 | public NamespacesLoader(Context context) { 20 | super(context); 21 | } 22 | 23 | @Override 24 | protected void onStartRequest() { 25 | ProximityApi.getInstance(getContext()) 26 | .getNamespaces(this, this); 27 | } 28 | 29 | @Override 30 | protected List onHandleResponse(JSONObject response) { 31 | //API Error 32 | if (response == null) return null; 33 | 34 | try { 35 | return Namespace.fromJson(response); 36 | } catch (JSONException e) { 37 | Log.w(TAG, "Parsing error", e); 38 | return null; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/data/Attachment.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.data; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Base64; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Model representation of a beacons.attachments resource 15 | */ 16 | public class Attachment { 17 | 18 | public static List fromJson(JSONObject object) throws JSONException { 19 | ArrayList items = new ArrayList<>(); 20 | 21 | JSONArray attachmentList = object.optJSONArray("attachments"); 22 | if (attachmentList != null) { 23 | for (int i = 0; i < attachmentList.length(); i++) { 24 | Attachment next = 25 | new Attachment(attachmentList.getJSONObject(i)); 26 | items.add(next); 27 | } 28 | } 29 | 30 | return items; 31 | } 32 | 33 | public final String name; 34 | public final String namespacedType; 35 | // The data field is always Base64 encoded 36 | public final String data; 37 | 38 | public Attachment(JSONObject object) { 39 | this.name = object.optString("attachmentName"); 40 | this.namespacedType = object.optString("namespacedType"); 41 | this.data = object.optString("data"); 42 | } 43 | 44 | public Attachment(String data, String namespacedType) { 45 | this.name = ""; 46 | this.namespacedType = namespacedType; 47 | this.data = encodeRawData(data); 48 | } 49 | 50 | private String encodeRawData(String raw) { 51 | return Base64.encodeToString(raw.getBytes(), Base64.NO_WRAP); 52 | } 53 | 54 | private String decodeData() { 55 | return new String(Base64.decode(this.data, Base64.NO_WRAP)); 56 | } 57 | 58 | public String getData() { 59 | return TextUtils.isEmpty(this.data) ? 60 | this.data : decodeData(); 61 | } 62 | 63 | public JSONObject toJson() throws JSONException { 64 | JSONObject object = new JSONObject(); 65 | if (!TextUtils.isEmpty(this.name)) { 66 | object.put("attachmentName", this.name); 67 | } 68 | object.put("namespacedType", this.namespacedType); 69 | object.put("data", this.data); 70 | 71 | return object; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return getData(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/data/AttachmentAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.data; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ArrayAdapter; 10 | import android.widget.TextView; 11 | 12 | import com.example.android.proximitymanager.R; 13 | import com.example.android.proximitymanager.api.ProximityApi; 14 | 15 | 16 | public class AttachmentAdapter extends ArrayAdapter implements 17 | View.OnClickListener { 18 | 19 | public AttachmentAdapter(Context context) { 20 | super(context, 0); 21 | } 22 | 23 | @Override 24 | public View getView(int position, View convertView, ViewGroup parent) { 25 | if (convertView == null) { 26 | convertView = LayoutInflater.from(getContext()) 27 | .inflate(R.layout.attachment_item, parent, false); 28 | } 29 | 30 | final Attachment item = getItem(position); 31 | TextView text1 = (TextView) convertView.findViewById(R.id.text1); 32 | TextView text2 = (TextView) convertView.findViewById(R.id.text2); 33 | text1.setText(item.getData()); 34 | text2.setText(item.namespacedType); 35 | 36 | View deleteButton = convertView.findViewById(R.id.button_delete); 37 | deleteButton.setOnClickListener(this); 38 | deleteButton.setTag(position); 39 | 40 | return convertView; 41 | } 42 | 43 | @Override 44 | public void onClick(View v) { 45 | int position = (Integer) v.getTag(); 46 | final Attachment item = getItem(position); 47 | new AlertDialog.Builder(getContext()) 48 | .setTitle("Are You Sure?") 49 | .setMessage("Do you want to delete " + item.name + " ?") 50 | .setNegativeButton("Cancel", null) 51 | .setPositiveButton("Delete", new DialogInterface.OnClickListener() { 52 | @Override 53 | public void onClick(DialogInterface dialog, int which) { 54 | ProximityApi.getInstance(getContext()) 55 | .deleteAttachment(item); 56 | } 57 | }) 58 | .show(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/data/BeaconAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.data; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ArrayAdapter; 8 | import android.widget.TextView; 9 | 10 | import com.example.android.proximitymanager.R; 11 | 12 | public class BeaconAdapter extends ArrayAdapter { 13 | 14 | public BeaconAdapter(Context context) { 15 | super(context, 0); 16 | } 17 | 18 | @Override 19 | public View getView(int position, View convertView, ViewGroup parent) { 20 | if (convertView == null) { 21 | convertView = LayoutInflater.from(getContext()) 22 | .inflate(R.layout.beacon_item, parent, false); 23 | } 24 | 25 | final Beacon item = getItem(position); 26 | TextView text1 = (TextView) convertView.findViewById(R.id.text1); 27 | TextView text2 = (TextView) convertView.findViewById(R.id.text2); 28 | text1.setText(item.description); 29 | text2.setText(String.format("%s: %s", item.name, item.status.toString())); 30 | 31 | return convertView; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/android/proximitymanager/data/Namespace.java: -------------------------------------------------------------------------------- 1 | package com.example.android.proximitymanager.data; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONException; 5 | import org.json.JSONObject; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Model representation of a namespaces resource 12 | */ 13 | public class Namespace { 14 | 15 | public static List fromJson(JSONObject object) throws JSONException { 16 | ArrayList items = new ArrayList<>(); 17 | 18 | JSONArray namespaces = object.getJSONArray("namespaces"); 19 | for (int i=0; i < namespaces.length(); i++) { 20 | items.add(new Namespace(namespaces.getJSONObject(i))); 21 | } 22 | 23 | return items; 24 | } 25 | 26 | public enum Visibility { 27 | UNLISTED, 28 | PUBLIC 29 | } 30 | 31 | public final String name; 32 | public final Visibility visibility; 33 | 34 | public Namespace(JSONObject object) { 35 | this.name = object.optString("namespaceName"); 36 | this.visibility = Visibility.valueOf(object.optString("servingVisibility")); 37 | } 38 | 39 | public String getCleanName() { 40 | return this.name.replace("namespaces/", ""); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | //Returned for adapter display only 46 | return getCleanName() + "/"; 47 | } 48 | 49 | public String getNamespacedType(String type) { 50 | return String.format("%s/%s", getCleanName(), type); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 16 |