├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── MIT License ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── raytw │ │ └── android │ │ └── ancssample │ │ └── ancsandroidsample │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── raytw │ │ │ └── android │ │ │ └── ancssample │ │ │ └── ancsandroidsample │ │ │ ├── ANCSGattCallback.java │ │ │ ├── ANCSParser.java │ │ │ ├── BLEservice.java │ │ │ ├── GattConstant.java │ │ │ ├── IOSNotification.java │ │ │ ├── Notice.java │ │ │ ├── ui │ │ │ ├── BLEConnectActivity.java │ │ │ └── BLEPeripheralListActivity.java │ │ │ └── util │ │ │ └── PermissionsRequest.java │ └── res │ │ ├── drawable-hdpi │ │ ├── btn_eztalk_b.png │ │ ├── btn_eztalk_b_p.png │ │ ├── ic_launcher.png │ │ ├── notification_contact_click_nsi3.xml │ │ └── notify_icon_default.png │ │ ├── drawable-mdpi │ │ ├── ic_launcher.png │ │ └── notify_bar_background.png │ │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ │ ├── layout │ │ ├── activity_devices.xml │ │ ├── activity_main.xml │ │ ├── ble_connect.xml │ │ ├── bledevice_list_item.xml │ │ ├── fragment_main.xml │ │ ├── notice.xml │ │ ├── notification_for_phone.xml │ │ └── notification_for_phone_initial.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 │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── raytw │ └── android │ └── ancssample │ └── ancsandroidsample │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ANCSAndroidSample -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 24 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 1.7 56 | 57 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ray Lee 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. -------------------------------------------------------------------------------- /MIT License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ANCSAndroidSample 2 | for iOS ANCS 3 | 4 | ## iOS phone 5 | 1. The BLE role using peripheral. 6 | 2. Must paired android. 7 | 8 | ## android phone 9 | 1. The BLE role using central. 10 | 2. Open app "ANCS_SIMPLE". 11 | 3. Click "scan". 12 | 4. Select and click iOS BLE deivce. 13 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.raytw.android.ancssample.ancsandroidsample" 9 | minSdkVersion 18 10 | targetSdkVersion 23 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 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.1' 26 | } 27 | -------------------------------------------------------------------------------- /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/leeray/Documents/ray/android-sdk-macosx/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/androidTest/java/com/raytw/android/ancssample/ancsandroidsample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/raytw/android/ancssample/ancsandroidsample/ANCSGattCallback.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample; 2 | 3 | import android.bluetooth.BluetoothGatt; 4 | import android.bluetooth.BluetoothGattCallback; 5 | import android.bluetooth.BluetoothGattCharacteristic; 6 | import android.bluetooth.BluetoothGattDescriptor; 7 | import android.bluetooth.BluetoothGattService; 8 | import android.bluetooth.BluetoothProfile; 9 | import android.content.Context; 10 | import android.util.Log; 11 | import android.widget.Toast; 12 | 13 | import java.util.ArrayList; 14 | import java.util.UUID; 15 | 16 | public class ANCSGattCallback extends BluetoothGattCallback { 17 | public static final int BleDisconnect = 0;// this is same to onConnectionStateChange()'s state 18 | public static final int BleAncsConnected = 10;// connected to iOS's ANCS 19 | public static final int BleBuildStart = 1;// after connectGatt(), before onConnectionStateChange() 20 | public static final int BleBuildConnectedGatt = 2; // onConnectionStateChange() state==2 21 | public static final int BleBuildDiscoverService = 3;// discoverServices()... this block 22 | public static final int BleBuildDiscoverOver = 4; // discoverServices() ok 23 | public static final int BleBuildDiscovered = 5; // discoverServices() BleBuildDiscovered callback 24 | public static final int BleBuildSetingANCS = 6; // settingANCS eg. need pwd... 25 | public static final int BleBuildNotify = 7; // notify arrive 26 | 27 | private String TAG = getClass().getSimpleName(); 28 | private Context mContext; 29 | public int mBleState; 30 | public static ANCSParser mANCSHandler; 31 | private BluetoothGatt mBluetoothGatt; 32 | BluetoothGattService mBluetoothGattService; 33 | boolean isWritedNS, isWriteNS_DespOk; 34 | private ArrayList mStateListenersList = new ArrayList(); 35 | 36 | public static interface StateListener { 37 | public void onStateChanged(int state); 38 | } 39 | 40 | public ANCSGattCallback(Context context, ANCSParser ancsParser) { 41 | mContext = context; 42 | mANCSHandler = ancsParser; 43 | } 44 | 45 | public void addStateListen(StateListener stateListener) { 46 | if (!mStateListenersList.contains(stateListener)) { 47 | mStateListenersList.add(stateListener); 48 | stateListener.onStateChanged(mBleState); 49 | } 50 | } 51 | 52 | 53 | public void stop() { 54 | Log.i(TAG, "stop connectGatt.."); 55 | mBleState = BleDisconnect; 56 | for (StateListener stateLlistener : mStateListenersList) { 57 | stateLlistener.onStateChanged(mBleState); 58 | } 59 | if (null != mBluetoothGatt) { 60 | mBluetoothGatt.disconnect(); 61 | mBluetoothGatt.close(); 62 | } 63 | mBluetoothGatt = null; 64 | mBluetoothGattService = null; 65 | mStateListenersList.clear(); 66 | } 67 | 68 | 69 | public void setBluetoothGatt(BluetoothGatt BluetoothGatt) { 70 | mBluetoothGatt = BluetoothGatt; 71 | } 72 | 73 | public void setStateStart() { 74 | mBleState = BleBuildStart; 75 | for (StateListener stateListener : mStateListenersList) { 76 | stateListener.onStateChanged(mBleState); 77 | } 78 | } 79 | 80 | public String getState() { 81 | String state = "[unknown]"; 82 | switch (mBleState) { 83 | case BleDisconnect: // 0 84 | state = "GATT [Disconnected]\n\n"; 85 | break; 86 | case BleBuildStart: // 1 87 | state = "waiting state change after connectGatt()\n\n"; 88 | break; 89 | case BleBuildConnectedGatt: // 2 90 | state = "GATT [Connected]\n\n"; 91 | break; 92 | case BleBuildDiscoverService: // 3 93 | state = "GATT [Connected]\n" + "discoverServices...\n"; 94 | break; 95 | case BleBuildDiscoverOver: // 4 96 | state = "GATT [Connected]\n" + "discoverServices OVER\n"; 97 | break; 98 | case BleBuildDiscovered: // 5 99 | state = "GATT [Connected]\n" + "BleBuildDiscovered\n"; 100 | break; 101 | case BleBuildSetingANCS: // 6 102 | state = "GATT [Connected]\n" + "discoverServices OVER\n" + "setting ANCS...password"; 103 | break; 104 | case BleBuildNotify: // 7 105 | state = "ANCS notify arrive\n"; 106 | 107 | break; 108 | case BleAncsConnected: // 10 109 | state = "GATT [Connected]\n" + "discoverServices OVER\n" + "ANCS[Connected] success !!"; 110 | break; 111 | } 112 | return state; 113 | } 114 | 115 | 116 | @Override 117 | public void onCharacteristicChanged(BluetoothGatt mBluetoothGatt, BluetoothGattCharacteristic mBluetoothGattCharacteristic) { 118 | UUID uuid = mBluetoothGattCharacteristic.getUuid(); 119 | Log.d(TAG, "test::onCharacteristicChanged,uuid[" + uuid + "]"); 120 | if (uuid.equals(GattConstant.Apple.sUUIDChaNotify)) { 121 | 122 | Log.i(TAG, "Notify uuid"); 123 | byte[] data = mBluetoothGattCharacteristic.getValue(); 124 | 125 | mANCSHandler.onNotification(data); 126 | 127 | mBleState = BleBuildNotify;// 6 128 | for (StateListener stateListener : mStateListenersList) { 129 | stateListener.onStateChanged(mBleState); 130 | } 131 | } else if (uuid.equals(GattConstant.Apple.sUUIDDataSource)) { 132 | 133 | byte[] data = mBluetoothGattCharacteristic.getValue(); 134 | mANCSHandler.onDSNotification(data); 135 | Log.i(TAG, "datasource uuid"); 136 | } else { 137 | } 138 | } 139 | 140 | @Override 141 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 142 | Log.i(TAG, "onConnectionStateChange,newState " + newState + "status:" + status); 143 | mBleState = newState; 144 | // below code is necessary? 145 | for (StateListener stateListener : mStateListenersList) { 146 | stateListener.onStateChanged(mBleState); 147 | } 148 | if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) { 149 | Log.i(TAG, "start discover service"); 150 | mBleState = BleBuildDiscoverService; 151 | 152 | mBluetoothGatt.discoverServices(); 153 | Log.i(TAG, "discovery service end"); 154 | mBleState = BleBuildDiscoverOver; 155 | for (StateListener stateListener : mStateListenersList) { 156 | stateListener.onStateChanged(mBleState); 157 | } 158 | } else if (0 == newState/* && mDisconnectReq */ && mBluetoothGatt != null) { 159 | } 160 | } 161 | 162 | @Override 163 | // New services discovered 164 | public void onServicesDiscovered(BluetoothGatt mBluetoothGatt, int status) { 165 | Log.d(TAG, "onServicesDiscovered,status[" + status + "]"); 166 | mBleState = BleBuildDiscovered; 167 | for (StateListener stateListener : mStateListenersList) { 168 | stateListener.onStateChanged(mBleState); 169 | } 170 | if (status != 0) 171 | return; 172 | 173 | BluetoothGattService bluetoothGattService = mBluetoothGatt.getService(GattConstant.Apple.sUUIDANCService); 174 | if (bluetoothGattService == null) { 175 | Log.i(TAG, "cannot find ANCS uuid"); 176 | return; 177 | } 178 | 179 | Log.i(TAG, "find ANCS service"); 180 | // Toast.makeText(mContext, "find ANCS service",Toast.LENGTH_LONG).show(); 181 | 182 | BluetoothGattCharacteristic bluetoothGattCharacteristic = bluetoothGattService.getCharacteristic(GattConstant.Apple.sUUIDDataSource); 183 | if (bluetoothGattCharacteristic == null) { 184 | Log.i(TAG, "cannot find DataSource(DS) characteristic"); 185 | return; 186 | } 187 | boolean registerDS = mBluetoothGatt.setCharacteristicNotification(bluetoothGattCharacteristic, true); 188 | if (!registerDS) { 189 | Log.i(TAG, " Enable (DS) notifications failed. "); 190 | return; 191 | } 192 | BluetoothGattDescriptor btDescriptor = bluetoothGattCharacteristic.getDescriptor(GattConstant.DESCRIPTOR_UUID); 193 | if (null != btDescriptor) { 194 | boolean r = btDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 195 | boolean rr = mBluetoothGatt.writeDescriptor(btDescriptor); 196 | 197 | Log.i(TAG, "Descriptoer setvalue " + r + "writeDescriptor() " + rr); 198 | } else { 199 | Log.i(TAG, "can not find descriptor from (DS)"); 200 | } 201 | isWriteNS_DespOk = isWritedNS = false; 202 | bluetoothGattCharacteristic = bluetoothGattService.getCharacteristic(GattConstant.Apple.sUUIDControl); 203 | if (bluetoothGattCharacteristic == null) { 204 | Log.i(TAG, "can not find ANCS's ControlPoint cha "); 205 | } 206 | 207 | mBluetoothGattService = bluetoothGattService; 208 | mANCSHandler.setService(bluetoothGattService, mBluetoothGatt); 209 | ANCSParser.get().reset(); 210 | Log.i(TAG, "found ANCS service & set DS character,descriptor OK !"); 211 | } 212 | 213 | @Override 214 | // the result of a descriptor write operation. 215 | public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { 216 | Log.d(TAG, "測試::onDescriptorWrite,status[" + status + "]"); 217 | Log.i(TAG, "onDescriptorWrite" + "status:" + status); 218 | 219 | if (15 == status || 5 == status) { 220 | mBleState = BleBuildSetingANCS;// 5 221 | for (StateListener stateListener : mStateListenersList) { 222 | stateListener.onStateChanged(mBleState); 223 | } 224 | return; 225 | } 226 | if (status != BluetoothGatt.GATT_SUCCESS) 227 | return; 228 | if (mContext != null) { 229 | if (status == 5 || status == 133) { 230 | Toast.makeText(mContext, "status = " + status, Toast.LENGTH_LONG).show(); 231 | ; 232 | } 233 | } 234 | // for some ble device, writedescriptor on sUUIDDataSource will return 133. fixme. 235 | // status is 0, SUCCESS. 236 | if (isWritedNS && isWriteNS_DespOk) { 237 | for (StateListener stateListener : mStateListenersList) { 238 | mBleState = BleAncsConnected; 239 | stateListener.onStateChanged(mBleState); 240 | } 241 | 242 | } 243 | if (mBluetoothGattService != null && !isWritedNS) { // set NS 244 | isWritedNS = true; 245 | BluetoothGattCharacteristic bluetoothGattCharacteristic = mBluetoothGattService.getCharacteristic(GattConstant.Apple.sUUIDChaNotify); 246 | if (bluetoothGattCharacteristic == null) { 247 | Log.i(TAG, "can not find ANCS's NS cha"); 248 | return; 249 | } else { 250 | } 251 | boolean registerNS = mBluetoothGatt.setCharacteristicNotification(bluetoothGattCharacteristic, true); 252 | if (!registerNS) { 253 | Log.i(TAG, " Enable (NS) notifications failed "); 254 | return; 255 | } 256 | BluetoothGattDescriptor desp = bluetoothGattCharacteristic.getDescriptor(GattConstant.DESCRIPTOR_UUID); 257 | if (null != desp) { 258 | boolean r = desp.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 259 | boolean rr = mBluetoothGatt.writeDescriptor(desp); 260 | isWriteNS_DespOk = rr; 261 | Log.i(TAG, "(NS)Descriptor.setValue(): " + r + ",writeDescriptor(): " + rr); 262 | } else { 263 | Log.i(TAG, "null descriptor"); 264 | } 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /app/src/main/java/com/raytw/android/ancssample/ancsandroidsample/ANCSParser.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample; 2 | 3 | import android.bluetooth.BluetoothGatt; 4 | import android.bluetooth.BluetoothGattCharacteristic; 5 | import android.bluetooth.BluetoothGattService; 6 | import android.content.Context; 7 | import android.os.Handler; 8 | import android.os.Message; 9 | import android.util.Log; 10 | 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.IOException; 13 | import java.util.ArrayList; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | 17 | public class ANCSParser { 18 | // ANCS constants 19 | public final static int NotificationAttributeIDAppIdentifier = 0; 20 | public final static int NotificationAttributeIDTitle = 1; // , (Needs to be followed by a 2-bytes 21 | // max length parameter) 22 | public final static int NotificationAttributeIDSubtitle = 2; // , (Needs to be followed by a 23 | // 2-bytes max length parameter) 24 | public final static int NotificationAttributeIDMessage = 3; // , (Needs to be followed by a 25 | // 2-bytes max length parameter) 26 | public final static int NotificationAttributeIDMessageSize = 4; // , 27 | public final static int NotificationAttributeIDDate = 5; // , 28 | public final static int AppAttributeIDDisplayName = 0; 29 | 30 | public final static int CommandIDGetNotificationAttributes = 0; 31 | public final static int CommandIDGetAppAttributes = 1; 32 | 33 | public final static int EventFlagSilent = (1 << 0); 34 | public final static int EventFlagImportant = (1 << 1); 35 | public final static int EventIDNotificationAdded = 0; 36 | public final static int EventIDNotificationModified = 1; 37 | public final static int EventIDNotificationRemoved = 2; 38 | 39 | public final static int CategoryIDOther = 0; 40 | public final static int CategoryIDIncomingCall = 1; 41 | public final static int CategoryIDMissedCall = 2; 42 | public final static int CategoryIDVoicemail = 3; 43 | public final static int CategoryIDSocial = 4; 44 | public final static int CategoryIDSchedule = 5; 45 | public final static int CategoryIDEmail = 6; 46 | public final static int CategoryIDNews = 7; 47 | public final static int CategoryIDHealthAndFitness = 8; 48 | public final static int CategoryIDBusinessAndFinance = 9; 49 | public final static int CategoryIDLocation = 10; 50 | public final static int CategoryIDEntertainment = 11; 51 | 52 | // !ANCS constants 53 | 54 | private final static int MSG_ADD_NOTIFICATION = 100; 55 | private final static int MSG_DO_NOTIFICATION = 101; 56 | private final static int MSG_RESET = 102; 57 | private final static int MSG_ERR = 103; 58 | private final static int MSG_CHECK_TIME = 104; 59 | private final static int MSG_FINISH = 105; 60 | private final static int FINISH_DELAY = 700; 61 | private final static int TIMEOUT = 15 * 1000; 62 | protected static final String TAG = "ANCSParser"; 63 | 64 | private List mANCSDataList = new LinkedList(); 65 | private Handler mHandler; 66 | 67 | private ANCSData mCurrentANCSData; 68 | BluetoothGatt mBTGatt; 69 | 70 | BluetoothGattService mBTGattService; 71 | Context mContext; 72 | private static ANCSParser mANCSParser; 73 | 74 | private ArrayList onIOSNotificationList = new ArrayList(); 75 | 76 | public interface onIOSNotification { 77 | void onIOSNotificationAdd(IOSNotification n); 78 | 79 | void onIOSNotificationRemove(int uid); 80 | } 81 | 82 | private ANCSParser(Context c) { 83 | mContext = c; 84 | mHandler = new Handler(c.getMainLooper()) { 85 | @Override 86 | public void handleMessage(Message msg) { 87 | int what = msg.what; 88 | if (MSG_CHECK_TIME == what) { 89 | if (mCurrentANCSData == null) { 90 | return; 91 | } 92 | if (System.currentTimeMillis() >= mCurrentANCSData.timeExpired) { 93 | 94 | Log.i(TAG, "msg timeout!"); 95 | } 96 | } else if (MSG_ADD_NOTIFICATION == what) { 97 | Log.e(TAG, "MSG_ADD_NOTIFICATION == what"); 98 | // mPendingNotifcations 可能需要每次收到前都要clear 99 | mANCSDataList.clear(); 100 | mANCSDataList.add(new ANCSData((byte[]) msg.obj)); 101 | mHandler.sendEmptyMessage(MSG_DO_NOTIFICATION); 102 | } else if (MSG_DO_NOTIFICATION == what) { 103 | processNotificationList(); 104 | } else if (MSG_RESET == what) { 105 | mHandler.removeMessages(MSG_ADD_NOTIFICATION); 106 | mHandler.removeMessages(MSG_DO_NOTIFICATION); 107 | mHandler.removeMessages(MSG_RESET); 108 | mHandler.removeMessages(MSG_ERR); 109 | mANCSDataList.clear(); 110 | mCurrentANCSData = null; 111 | 112 | Log.i(TAG, "ANCSHandler reseted"); 113 | } else if (MSG_ERR == what) { 114 | 115 | Log.i(TAG, "error,skip_cur_data"); 116 | mCurrentANCSData.clear(); 117 | mCurrentANCSData = null; 118 | mHandler.sendEmptyMessage(MSG_DO_NOTIFICATION); 119 | } else if (MSG_FINISH == what) { 120 | Log.i(TAG, "msg data.finish()"); 121 | if (null != mCurrentANCSData) 122 | mCurrentANCSData.finish(); 123 | } 124 | } 125 | }; 126 | } 127 | 128 | public void listenIOSNotification(onIOSNotification onIOSNotify) { 129 | if (!onIOSNotificationList.contains(onIOSNotify)) 130 | onIOSNotificationList.add(onIOSNotify); 131 | } 132 | 133 | public void unlistenIOSNotification(onIOSNotification onIOSNotify) { 134 | onIOSNotificationList.remove(onIOSNotify); 135 | } 136 | 137 | public void setService(BluetoothGattService btGattService, BluetoothGatt btGatt) { 138 | mBTGatt = btGatt; 139 | mBTGattService = btGattService; 140 | } 141 | 142 | public static ANCSParser getDefault(Context context) { 143 | if (mANCSParser == null) { 144 | mANCSParser = new ANCSParser(context); 145 | } 146 | return mANCSParser; 147 | } 148 | 149 | public static ANCSParser get() { 150 | return mANCSParser; 151 | } 152 | 153 | private void sendNotification(final IOSNotification notiification) { 154 | Log.i(TAG, "[Add Notification] : " + notiification.uid); 155 | for (onIOSNotification notificationItem : onIOSNotificationList) { 156 | notificationItem.onIOSNotificationAdd(notiification); 157 | } 158 | } 159 | 160 | private void cancelNotification(int uid) { 161 | Log.i(TAG, "[cancel Notification] : " + uid); 162 | for (onIOSNotification onNotificationItem : onIOSNotificationList) { 163 | onNotificationItem.onIOSNotificationRemove(uid); 164 | } 165 | } 166 | 167 | private class ANCSData { 168 | long timeExpired; 169 | int curStep = 0; 170 | 171 | final byte[] notifyData; // 8 bytes 172 | 173 | ByteArrayOutputStream byteArrayOutputStream; 174 | IOSNotification notification; 175 | 176 | ANCSData(byte[] data) { 177 | notifyData = data; 178 | curStep = 0; 179 | timeExpired = System.currentTimeMillis(); 180 | notification = new IOSNotification(); 181 | } 182 | 183 | void clear() { 184 | if (byteArrayOutputStream != null) { 185 | byteArrayOutputStream.reset(); 186 | } 187 | byteArrayOutputStream = null; 188 | curStep = 0; 189 | } 190 | 191 | int getUID() { 192 | return (0xff & notifyData[7] << 24) | (0xff & notifyData[6] << 16) | (0xff & notifyData[5] << 8) | (0xff & notifyData[4]); 193 | } 194 | 195 | void finish() { 196 | Log.d(TAG, "finish"); 197 | if (null == byteArrayOutputStream) { 198 | return; 199 | } 200 | 201 | final byte[] data = byteArrayOutputStream.toByteArray(); 202 | logD(data); 203 | if (data.length < 5) { 204 | return; // 205 | } 206 | // check if finished ? 207 | int cmdId = data[0]; // should be 0 //0 commandID 208 | if (cmdId != 0) { 209 | Log.i(TAG, "bad cmdId: " + cmdId); 210 | return; 211 | } 212 | int uid = ((0xff & data[4]) << 24) | ((0xff & data[3]) << 16) | ((0xff & data[2]) << 8) | ((0xff & data[1])); 213 | if (uid != mCurrentANCSData.getUID()) { 214 | 215 | Log.i(TAG, "bad uid: " + uid + "->" + mCurrentANCSData.getUID()); 216 | return; 217 | } 218 | 219 | // read attributes 220 | notification.uid = uid; 221 | int curIdx = 5; // hard code 222 | while (true) { 223 | if (notification.isAllInit()) { 224 | break; 225 | } 226 | if (data.length < curIdx + 3) { 227 | return; 228 | } 229 | // attributes head 230 | int attrId = data[curIdx]; 231 | int attrLen = ((data[curIdx + 1]) & 0xFF) | (0xFF & (data[curIdx + 2] << 8)); 232 | curIdx += 3; 233 | if (data.length < curIdx + attrLen) { 234 | return; 235 | } 236 | String val = new String(data, curIdx, attrLen);// utf-8 encode 237 | if (attrId == NotificationAttributeIDTitle) { 238 | notification.title = val; 239 | } else if (attrId == NotificationAttributeIDMessage) { 240 | notification.message = val; 241 | } else if (attrId == NotificationAttributeIDDate) { 242 | notification.date = val; 243 | } else if (attrId == NotificationAttributeIDSubtitle) { 244 | notification.subtitle = val; 245 | } else if (attrId == NotificationAttributeIDMessageSize) { 246 | notification.messageSize = val; 247 | } 248 | curIdx += attrLen; 249 | } 250 | Log.i(TAG, "noti.title:" + notification.title); 251 | Log.i(TAG, "noti.message:" + notification.message); 252 | Log.i(TAG, "noti.date:" + notification.date); 253 | Log.i(TAG, "noti.subtitle:" + notification.subtitle); 254 | Log.i(TAG, "noti.messageSize:" + notification.messageSize); 255 | Log.i(TAG, "got a notification! data size = " + data.length); 256 | mCurrentANCSData = null; 257 | // mHandler.sendEmptyMessage(MSG_DO_NOTIFICATION); // continue next! 258 | sendNotification(notification); 259 | } 260 | } 261 | 262 | 263 | private void processNotificationList() { 264 | 265 | Log.d(TAG, "1 processNotificationList==>mCurData" + mCurrentANCSData); 266 | mHandler.removeMessages(MSG_DO_NOTIFICATION); 267 | // handle curData! 268 | if (mCurrentANCSData == null) { 269 | if (mANCSDataList.size() == 0) { 270 | return; 271 | } 272 | 273 | mCurrentANCSData = mANCSDataList.remove(0); 274 | mANCSDataList.clear(); 275 | Log.i(TAG, "ANCS New CurData"); 276 | } else if (mCurrentANCSData.curStep == 0) { // parse notify data 277 | Log.d(TAG, "2 processNotificationList==>mCurData" + mCurrentANCSData); 278 | do { 279 | if (mCurrentANCSData.notifyData == null || mCurrentANCSData.notifyData.length != 8) { 280 | mCurrentANCSData = null; // ignore 281 | 282 | Log.i(TAG, "ANCS Bad Head!"); 283 | break; 284 | } 285 | if (EventIDNotificationRemoved == mCurrentANCSData.notifyData[0]) { 286 | Log.d(TAG, "3 processNotificationList==>mCurData" + mCurrentANCSData); 287 | int uid = 288 | (mCurrentANCSData.notifyData[4] & 0xff) | (mCurrentANCSData.notifyData[5] & 0xff << 8) | (mCurrentANCSData.notifyData[6] & 0xff << 16) | (mCurrentANCSData.notifyData[7] & 0xff << 24); 289 | cancelNotification(uid); 290 | mCurrentANCSData = null; 291 | break; 292 | } 293 | if (EventIDNotificationAdded != mCurrentANCSData.notifyData[0]) { 294 | Log.d(TAG, "4 processNotificationList==>mCurData" + mCurrentANCSData); 295 | mCurrentANCSData = null; // ignore 296 | Log.i(TAG, "ANCS NOT Add!"); 297 | break; 298 | } 299 | Log.d(TAG, "5 processNotificationList==>mCurData" + mCurrentANCSData); 300 | // get attribute if needed! 301 | BluetoothGattCharacteristic btGattcharacteristic = mBTGattService.getCharacteristic(GattConstant.Apple.sUUIDControl); 302 | Log.d(TAG, "6 processNotificationList==>cha" + btGattcharacteristic); 303 | if (null != btGattcharacteristic) { 304 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 305 | 306 | bout.write((byte) 0); 307 | 308 | bout.write(mCurrentANCSData.notifyData[4]); 309 | bout.write(mCurrentANCSData.notifyData[5]); 310 | bout.write(mCurrentANCSData.notifyData[6]); 311 | bout.write(mCurrentANCSData.notifyData[7]); 312 | 313 | 314 | bout.write(NotificationAttributeIDTitle); 315 | bout.write(50); 316 | bout.write(0); 317 | // subtitle 318 | bout.write(NotificationAttributeIDSubtitle); 319 | bout.write(100); 320 | bout.write(0); 321 | 322 | // message 323 | bout.write(NotificationAttributeIDMessage); 324 | bout.write(500); 325 | bout.write(0); 326 | 327 | // message size 328 | bout.write(NotificationAttributeIDMessageSize); 329 | bout.write(10); 330 | bout.write(0); 331 | // date 332 | bout.write(NotificationAttributeIDDate); 333 | bout.write(10); 334 | bout.write(0); 335 | 336 | byte[] data = bout.toByteArray(); 337 | 338 | btGattcharacteristic.setValue(data); 339 | mBTGatt.writeCharacteristic(btGattcharacteristic); 340 | Log.i(TAG, "request ANCS(CP) the data of Notification. = "); 341 | mCurrentANCSData.curStep = 1; 342 | mCurrentANCSData.byteArrayOutputStream = new ByteArrayOutputStream(); 343 | mCurrentANCSData.timeExpired = System.currentTimeMillis() + TIMEOUT; 344 | // mHandler.removeMessages(MSG_CHECK_TIME); 345 | // mHandler.sendEmptyMessageDelayed(MSG_CHECK_TIME, TIMEOUT); 346 | return; 347 | } else { 348 | Log.i(TAG, "ANCS has No Control Point !"); 349 | // has no control!// just vibrate ... 350 | mCurrentANCSData.byteArrayOutputStream = null; 351 | mCurrentANCSData.curStep = 1; 352 | } 353 | 354 | } while (false); 355 | } else if (mCurrentANCSData.curStep == 1) { 356 | // check if finished! 357 | // mCurData.finish(); 358 | Log.d(TAG, "7 processNotificationList==>mCurData" + mCurrentANCSData); 359 | return; 360 | } else { 361 | Log.d(TAG, "8 processNotificationList==>mCurData" + mCurrentANCSData); 362 | return; 363 | } 364 | Log.d(TAG, "9 processNotificationList==>mCurData" + mCurrentANCSData); 365 | mHandler.sendEmptyMessage(MSG_DO_NOTIFICATION); // do next step 366 | } 367 | 368 | 369 | public void onDSNotification(byte[] data) { 370 | if (mCurrentANCSData == null) { 371 | 372 | Log.i(TAG, "got ds notify without cur data"); 373 | return; 374 | } 375 | try { 376 | mHandler.removeMessages(MSG_FINISH); 377 | mCurrentANCSData.byteArrayOutputStream.write(data); 378 | mHandler.sendEmptyMessageDelayed(MSG_FINISH, FINISH_DELAY); 379 | } catch (IOException e) { 380 | Log.i(TAG, e.toString()); 381 | } 382 | } 383 | 384 | void onWrite(BluetoothGattCharacteristic characteristic, int status) { 385 | if (status != BluetoothGatt.GATT_SUCCESS) { 386 | Log.i(TAG, "write err: " + status); 387 | mHandler.sendEmptyMessage(MSG_ERR); 388 | } else { 389 | Log.i(TAG, "write OK"); 390 | mHandler.sendEmptyMessage(MSG_DO_NOTIFICATION); 391 | } 392 | } 393 | 394 | public void onNotification(byte[] data) { 395 | Log.e(TAG, "onNotification..."); 396 | if (data == null || data.length != 8) { 397 | Log.i(TAG, "bad ANCS notification data"); 398 | return; 399 | } 400 | logD(data); 401 | Message msg = mHandler.obtainMessage(MSG_ADD_NOTIFICATION); 402 | msg.obj = data; 403 | msg.sendToTarget(); 404 | } 405 | 406 | public void reset() { 407 | mHandler.sendEmptyMessage(MSG_RESET); 408 | } 409 | 410 | void logD(byte[] d) { 411 | StringBuffer sb = new StringBuffer(); 412 | int len = d.length; 413 | for (int i = 0; i < len; i++) { 414 | sb.append(d[i] + ", "); 415 | } 416 | Log.i(TAG, "log Data size[" + len + "] : " + sb); 417 | } 418 | 419 | } 420 | -------------------------------------------------------------------------------- /app/src/main/java/com/raytw/android/ancssample/ancsandroidsample/BLEservice.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample; 2 | 3 | 4 | import android.annotation.SuppressLint; 5 | import android.app.NotificationManager; 6 | import android.app.Service; 7 | import android.bluetooth.BluetoothAdapter; 8 | import android.bluetooth.BluetoothDevice; 9 | import android.bluetooth.BluetoothGatt; 10 | import android.bluetooth.BluetoothGattService; 11 | import android.content.BroadcastReceiver; 12 | import android.content.Context; 13 | import android.content.Intent; 14 | import android.content.IntentFilter; 15 | import android.content.SharedPreferences.Editor; 16 | import android.media.RingtoneManager; 17 | import android.net.Uri; 18 | import android.os.Binder; 19 | import android.os.Handler; 20 | import android.os.IBinder; 21 | import android.os.Message; 22 | import android.support.v4.app.NotificationCompat; 23 | import android.util.Log; 24 | 25 | import com.raytw.android.ancssample.ancsandroidsample.ANCSGattCallback.StateListener; 26 | import com.raytw.android.ancssample.ancsandroidsample.ui.BLEPeripheralListActivity; 27 | 28 | import java.util.List; 29 | 30 | public class BLEservice extends Service implements ANCSParser.onIOSNotification, ANCSGattCallback.StateListener { 31 | private String TAG = getClass().getSimpleName(); 32 | private final IBinder mBinder = new MyBinder(); 33 | private ANCSParser mANCSHandler; 34 | private ANCSGattCallback mANCSGCattCallback; 35 | BluetoothGatt mBluetoothGatt; 36 | BroadcastReceiver mBtOnOffReceiver; 37 | boolean isAuto; 38 | String addr; 39 | private int mBleANCSstate = 0; 40 | 41 | public class MyBinder extends Binder { 42 | public BLEservice getService() { 43 | // Return this instance so clients can call public methods 44 | return BLEservice.this; 45 | } 46 | } 47 | 48 | @SuppressLint("HandlerLeak") 49 | private Handler mHandler = new Handler() { 50 | @Override 51 | public void handleMessage(Message msg) { 52 | switch (msg.what) { 53 | case 11: // bt off, stopSelf() 54 | stopSelf(); 55 | startActivityMsg(); 56 | break; 57 | } 58 | } 59 | }; 60 | 61 | // when bt off, show a Message to notify user that ble need re_connect 62 | private void startActivityMsg() { 63 | Intent i = new Intent(this, Notice.class); 64 | i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 65 | startActivity(i); 66 | } 67 | 68 | @Override 69 | public void onCreate() { 70 | super.onCreate(); 71 | Log.i(TAG, "onCreate"); 72 | mANCSHandler = ANCSParser.getDefault(this); 73 | mANCSGCattCallback = new ANCSGattCallback(this, mANCSHandler); 74 | mBtOnOffReceiver = new BroadcastReceiver() { 75 | public void onReceive(Context arg0, Intent intent) { 76 | // action must be bt on/off . 77 | int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 78 | if (state == BluetoothAdapter.STATE_OFF) { 79 | Log.i(TAG, "bluetooth OFF !"); 80 | mHandler.sendEmptyMessageDelayed(11, 500); 81 | } 82 | } 83 | }; 84 | IntentFilter filter = new IntentFilter(); 85 | filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);// bt on/off 86 | registerReceiver(mBtOnOffReceiver, filter); 87 | } 88 | 89 | @Override 90 | public int onStartCommand(Intent intent, int flags, int startId) { 91 | if (intent != null) { 92 | isAuto = intent.getBooleanExtra("auto", true); 93 | addr = intent.getStringExtra("addr"); 94 | } 95 | Log.i(TAG, "onStartCommand() flags=" + flags + ",stardId=" + startId); 96 | return START_STICKY_COMPATIBILITY; 97 | // return startId; 98 | } 99 | 100 | @Override 101 | public void onDestroy() { 102 | Log.i(TAG, " onDestroy()"); 103 | mANCSGCattCallback.stop(); 104 | mANCSHandler.unlistenIOSNotification(this); 105 | unregisterReceiver(mBtOnOffReceiver); 106 | Editor editor = getSharedPreferences(BLEPeripheralListActivity.PREFS_NAME, 0).edit(); 107 | editor.putInt(BLEPeripheralListActivity.BleStateKey, ANCSGattCallback.BleDisconnect); 108 | editor.commit(); 109 | super.onDestroy(); 110 | } 111 | 112 | @Override 113 | public IBinder onBind(Intent i) { 114 | Log.i(TAG, " onBind()thread id =" + android.os.Process.myTid()); 115 | return mBinder; 116 | } 117 | 118 | // ** when ios notification changed 119 | @Override 120 | public void onIOSNotificationAdd(IOSNotification noti) { 121 | NotificationCompat.Builder build = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher) 122 | .setContentTitle(noti.title).setContentText(noti.message); 123 | build.setTicker(noti.title); 124 | Log.e(TAG, "noti.title===>" + noti.title); 125 | Log.e(TAG, "noti.message===>" + noti.message); 126 | // set default sound 127 | Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); 128 | build.setSound(uri); 129 | ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(noti.uid, build.build()); 130 | } 131 | 132 | @Override 133 | public void onIOSNotificationRemove(int uid) { 134 | ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancel(uid); 135 | } 136 | 137 | // ** public method , for client to call 138 | public void startBleConnect(String addr, boolean auto) { 139 | Log.i(TAG, "startBleConnect-begin-"); 140 | if (mBleANCSstate != 0) { 141 | Log.i(TAG, "stop ancs,then restart it"); 142 | mANCSGCattCallback.stop(); 143 | } 144 | isAuto = auto; 145 | this.addr = addr; 146 | BluetoothDevice dev = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(addr); 147 | mANCSHandler.listenIOSNotification(this); 148 | mBluetoothGatt = dev.connectGatt(this, auto, mANCSGCattCallback); 149 | mANCSGCattCallback.setBluetoothGatt(mBluetoothGatt); 150 | mANCSGCattCallback.setStateStart(); 151 | Log.i(TAG, "startBleConnect-end-(waiting callback)"); 152 | } 153 | 154 | public void registerStateChanged(StateListener stateListener) { 155 | Log.i(TAG, "registerStateChanged"); 156 | if (null != stateListener) 157 | mANCSGCattCallback.addStateListen(stateListener); 158 | mANCSGCattCallback.addStateListen(this); 159 | } 160 | 161 | public void connect() { 162 | if (!isAuto) 163 | mBluetoothGatt.connect(); 164 | } 165 | 166 | public String getStateDes() { 167 | return mANCSGCattCallback.getState(); 168 | } 169 | 170 | public List getBluetoothGattServices(){ 171 | return mBluetoothGatt.getServices(); 172 | } 173 | 174 | public int getBleANCSstate() { 175 | return mBleANCSstate; 176 | } 177 | 178 | @Override 179 | public void onStateChanged(int state) { 180 | mBleANCSstate = state; 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /app/src/main/java/com/raytw/android/ancssample/ancsandroidsample/GattConstant.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * ANCS 機制和格式說明 https://developer.apple.com/library/ios/documentation/CoreBluetooth/Reference/ 7 | * AppleNotificationCenterServiceSpecification 8 | * /Specification/Specification.html#//apple_ref/doc/uid/TP40013460-CH1-SW7 9 | */ 10 | 11 | public class GattConstant { 12 | public static final UUID DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); 13 | 14 | public static class Apple { 15 | /** 16 | * iphone ANCS service UUID 17 | */ 18 | public static final UUID sUUIDANCService = UUID.fromString("7905F431-B5CE-4E99-A40F-4B1E122D00D0"); 19 | /** 20 | * Notification Source: UUID 21 | */ 22 | public static final UUID sUUIDChaNotify = UUID.fromString("9FBF120D-6301-42D9-8C58-25E699A21DBD"); 23 | /** 24 | * Control Point: UUID 25 | */ 26 | public static final UUID sUUIDControl = UUID.fromString("69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9"); 27 | /** 28 | * Data Source: UUID 29 | */ 30 | public static final UUID sUUIDDataSource = UUID.fromString("22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB"); 31 | } 32 | 33 | static String getNameforUUID(UUID uuid) { 34 | String name = "unknown"; 35 | if (0 == uuid.compareTo(Apple.sUUIDChaNotify)) { 36 | name = "ANCS's NS"; 37 | } else if (0 == uuid.compareTo(Apple.sUUIDControl)) { 38 | name = "ANCS's CP"; 39 | } else if (0 == uuid.compareTo(Apple.sUUIDDataSource)) { 40 | name = "ANCS's DS"; 41 | } else if (0 == uuid.compareTo(DESCRIPTOR_UUID)) { 42 | name = "UpdateDescriptor"; 43 | } 44 | return name; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/raytw/android/ancssample/ancsandroidsample/IOSNotification.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample; 2 | 3 | 4 | /** 5 | * a notice from iPhone ANCS
6 | */ 7 | public class IOSNotification { 8 | /** 9 | * the unique identifier (UID) for the iOS notification 10 | */ 11 | public int uid; 12 | /** 13 | * title for the iOS notification 14 | */ 15 | public String title; 16 | /** 17 | * subtitle for the iOS notification 18 | */ 19 | public String subtitle; 20 | /** 21 | * message(content) for the iOS notification 22 | */ 23 | public String message; 24 | /** 25 | * size (how many byte) of message 26 | */ 27 | public String messageSize; 28 | /** 29 | * the time for the iOS notification 30 | */ 31 | public String date; 32 | 33 | public IOSNotification() { 34 | } 35 | 36 | public IOSNotification(String t, String s, String m, String ms, String d) { 37 | title = t; 38 | subtitle = s; 39 | message = m; 40 | messageSize = ms; 41 | date = d; 42 | } 43 | 44 | boolean isAllInit() { 45 | return title != null && subtitle != null && message != null && messageSize != null && date != null; 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/raytw/android/ancssample/ancsandroidsample/Notice.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample; 2 | 3 | // import com.burns.android.ancssample.R; 4 | 5 | import android.app.Activity; 6 | import android.os.Bundle; 7 | 8 | public class Notice extends Activity { 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | setContentView(R.layout.notice); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/raytw/android/ancssample/ancsandroidsample/ui/BLEConnectActivity.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample.ui; 2 | 3 | 4 | import android.app.Activity; 5 | import android.bluetooth.BluetoothAdapter; 6 | import android.bluetooth.BluetoothGattService; 7 | import android.content.BroadcastReceiver; 8 | import android.content.ComponentName; 9 | import android.content.Context; 10 | import android.content.Intent; 11 | import android.content.IntentFilter; 12 | import android.content.ServiceConnection; 13 | import android.content.SharedPreferences; 14 | import android.os.Bundle; 15 | import android.os.IBinder; 16 | import android.text.method.ScrollingMovementMethod; 17 | import android.util.Log; 18 | import android.view.View; 19 | import android.widget.TextView; 20 | import android.widget.Toast; 21 | 22 | import com.raytw.android.ancssample.ancsandroidsample.ANCSGattCallback; 23 | import com.raytw.android.ancssample.ancsandroidsample.ANCSGattCallback.StateListener; 24 | import com.raytw.android.ancssample.ancsandroidsample.BLEservice; 25 | import com.raytw.android.ancssample.ancsandroidsample.BLEservice.MyBinder; 26 | import com.raytw.android.ancssample.ancsandroidsample.GattConstant; 27 | import com.raytw.android.ancssample.ancsandroidsample.R; 28 | 29 | import java.util.List; 30 | 31 | 32 | public class BLEConnectActivity extends Activity implements StateListener { 33 | private String TAG = getClass().getSimpleName(); 34 | SharedPreferences mSharedPreference; 35 | String address; 36 | boolean isAuto; // whether connectGatt(,auto,) 37 | boolean isBond; 38 | TextView mStateText; 39 | BLEservice mBLEservice; 40 | Intent mIntent; 41 | int mCachedState; 42 | BroadcastReceiver mBtOnOffReceiver; 43 | 44 | @Override 45 | public void onCreate(Bundle b) { 46 | super.onCreate(b); 47 | Log.i(TAG, "onCreate"); 48 | 49 | setContentView(R.layout.ble_connect); 50 | mStateText = (TextView) findViewById(R.id.ble_state); 51 | mStateText.setMovementMethod(new ScrollingMovementMethod()); 52 | 53 | address = getIntent().getStringExtra("addr"); 54 | isAuto = getIntent().getBooleanExtra("auto", true); 55 | 56 | mSharedPreference = getSharedPreferences(BLEPeripheralListActivity.PREFS_NAME, 0); 57 | 58 | Log.e(TAG, "mAuto:" + isAuto); 59 | 60 | if (!isAuto) { 61 | mStateText.setOnClickListener(new View.OnClickListener() { 62 | @Override 63 | public void onClick(View arg0) { 64 | if (null != mBLEservice) { 65 | mBLEservice.connect(); 66 | Toast.makeText(BLEConnectActivity.this, R.string.connect_notice, Toast.LENGTH_SHORT).show(); 67 | } 68 | } 69 | }); 70 | } 71 | 72 | mCachedState = getIntent().getIntExtra("state", 0); 73 | mIntent = new Intent(this, BLEservice.class); 74 | mIntent.putExtra("addr", address); 75 | mIntent.putExtra("auto", isAuto); 76 | startService(mIntent); 77 | 78 | mBtOnOffReceiver = new BroadcastReceiver() { 79 | public void onReceive(Context arg0, Intent intent) { 80 | // action must be bt on/off . 81 | String act = intent.getAction(); 82 | if (act.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 83 | int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 84 | if (state != BluetoothAdapter.STATE_ON) { 85 | finish(); 86 | } 87 | } 88 | } 89 | }; 90 | } 91 | 92 | @Override 93 | public void onStart() { 94 | 95 | super.onStart(); 96 | Log.i(TAG, "onStart"); 97 | IntentFilter filter = new IntentFilter(); 98 | filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);// bt on/off 99 | 100 | registerReceiver(mBtOnOffReceiver, filter); 101 | } 102 | 103 | @Override 104 | public void onResume() { 105 | super.onResume(); 106 | Log.i(TAG, "onResume"); 107 | bindService(mIntent, conn, BIND_AUTO_CREATE); 108 | } 109 | 110 | @Override 111 | public void onStop() { 112 | Log.i(TAG, "onStop"); 113 | unregisterReceiver(mBtOnOffReceiver); 114 | unbindService(conn); 115 | stopService(mIntent); 116 | super.onStop(); 117 | } 118 | 119 | ServiceConnection conn = new ServiceConnection() { 120 | 121 | @Override 122 | public void onServiceConnected(ComponentName cn, IBinder binder) { 123 | Log.i(TAG, "onServiceConnected"); 124 | MyBinder mbinder = (MyBinder) binder; 125 | mBLEservice = mbinder.getService(); 126 | isBond = true; 127 | startConnectGatt();// now not connect , 128 | 129 | } 130 | 131 | @Override 132 | public void onServiceDisconnected(ComponentName cn) { 133 | isBond = false; 134 | Log.i(TAG, "onServiceDisconnected"); 135 | } 136 | }; 137 | 138 | private void startConnectGatt() { 139 | // FIXME: there is a bug in here. 140 | Log.i(TAG, "startConnectGatt " + "mCachedState:" + mCachedState + "getmBleANCS_state:" + mBLEservice.getBleANCSstate()); 141 | if (mBLEservice.getBleANCSstate() != ANCSGattCallback.BleDisconnect) { 142 | final String str = mBLEservice.getStateDes(); 143 | mStateText.append("startConnectGatt:"+str + "\n"); 144 | } else if (ANCSGattCallback.BleDisconnect == mCachedState) { 145 | Log.i(TAG, "connect ble"); 146 | mBLEservice.startBleConnect(address, isAuto); 147 | mBLEservice.registerStateChanged(this); 148 | } else { // just display current state 149 | 150 | final String str = mBLEservice.getStateDes(); 151 | mStateText.append("startConnectGatt:"+str + "\n"); 152 | } 153 | } 154 | 155 | @Override 156 | public void onStateChanged(final int state) { 157 | runOnUiThread(new Runnable() { 158 | public void run() { 159 | mStateText.append("onStateChanged:" + mBLEservice.getStateDes() + "\n"); 160 | } 161 | }); 162 | 163 | if(state == ANCSGattCallback.BleBuildDiscovered){ 164 | List bleServices = mBLEservice.getBluetoothGattServices(); 165 | final StringBuffer strBuf = new StringBuffer(); 166 | 167 | for(BluetoothGattService service : bleServices){ 168 | if(GattConstant.Apple.sUUIDANCService.equals(service.getUuid())){ 169 | strBuf.append(service.getUuid() + " => ANCS" + "\n"); 170 | }else{ 171 | strBuf.append(service.getUuid() + "\n"); 172 | } 173 | } 174 | Log.d(TAG, "bleServices,size["+bleServices.size()+"]"); 175 | if(strBuf.length() > 0){ 176 | runOnUiThread(new Runnable() { 177 | public void run() { 178 | mStateText.append("allServicesUUID-----begin-----\n"); 179 | mStateText.append(strBuf.toString()); 180 | mStateText.append("allServicesUUID-----end-----\n"); 181 | } 182 | }); 183 | return; 184 | } 185 | } 186 | } 187 | 188 | public void onBackPressed() { 189 | Intent intent = new Intent(this, BLEPeripheralListActivity.class); 190 | startActivity(intent); 191 | finish(); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /app/src/main/java/com/raytw/android/ancssample/ancsandroidsample/ui/BLEPeripheralListActivity.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample.ui; 2 | 3 | import android.Manifest; 4 | import android.app.ListActivity; 5 | import android.bluetooth.BluetoothAdapter; 6 | import android.bluetooth.BluetoothAdapter.LeScanCallback; 7 | import android.bluetooth.BluetoothDevice; 8 | import android.bluetooth.BluetoothManager; 9 | import android.content.Context; 10 | import android.content.Intent; 11 | import android.content.SharedPreferences; 12 | import android.content.pm.PackageManager; 13 | import android.os.Bundle; 14 | import android.text.TextUtils; 15 | import android.util.Log; 16 | import android.view.LayoutInflater; 17 | import android.view.MenuItem; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.widget.BaseAdapter; 21 | import android.widget.Button; 22 | import android.widget.ListView; 23 | import android.widget.TextView; 24 | import android.widget.Toast; 25 | 26 | import com.raytw.android.ancssample.ancsandroidsample.R; 27 | import com.raytw.android.ancssample.ancsandroidsample.util.PermissionsRequest; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | 33 | public class BLEPeripheralListActivity extends ListActivity { 34 | 35 | public String TAG = getClass().getSimpleName(); 36 | private PermissionsRequest mPermissionsRequest; 37 | public static final String PREFS_NAME = "MyPrefsFile"; 38 | public static final String BleStateKey = "ble_state"; 39 | public static final String BleAddrKey = "ble_addr"; 40 | public static final String BleAutoKey = "ble_auto_connect"; 41 | private BluetoothAdapter mBluetoothAdapter; 42 | private boolean isBLEScaning = false; 43 | private Button buttonScan; 44 | private List mBluetoothDeviceList = new ArrayList(); 45 | private BLEDeviceListAdapter mListAdapter = new BLEDeviceListAdapter(); 46 | 47 | private LeScanCallback mLEScanCallback = new LeScanCallback() { 48 | 49 | @Override 50 | public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { 51 | runOnUiThread(new Runnable() { 52 | @Override 53 | public void run() { 54 | boolean found = false; 55 | for (BluetoothDevice dev : mBluetoothDeviceList) { 56 | if (dev.getAddress().equals(device.getAddress())) { 57 | Log.i(TAG, "found ble device:" + device.getName()); 58 | found = true; 59 | break; 60 | } 61 | } 62 | if (!found) { 63 | mBluetoothDeviceList.add(device); 64 | Log.v(TAG, "name[" + device.getName() + "],device=>" + device.getAddress()); 65 | mListAdapter.notifyDataSetChanged(); 66 | } 67 | } 68 | }); 69 | 70 | } 71 | }; 72 | 73 | @Override 74 | protected void onCreate(Bundle savedInstanceState) { 75 | super.onCreate(savedInstanceState); 76 | setContentView(R.layout.activity_devices); 77 | mPermissionsRequest = buildPermissionsRequest(); 78 | mPermissionsRequest.doCheckPermission(false); 79 | 80 | buttonScan = (Button) findViewById(R.id.scan); 81 | buttonScan.setOnClickListener(new View.OnClickListener() { 82 | @Override 83 | public void onClick(View arg0) { 84 | if (PermissionsRequest.isPermissionGranted( 85 | BLEPeripheralListActivity.this, 86 | Manifest.permission.ACCESS_FINE_LOCATION)) { 87 | if (!isBLEScaning) { 88 | mBluetoothDeviceList.clear(); 89 | scan(true); 90 | } else { 91 | scan(false); 92 | } 93 | } else { 94 | Toast.makeText(getApplicationContext(), 95 | "please setting app permission", Toast.LENGTH_SHORT) 96 | .show(); 97 | } 98 | } 99 | }); 100 | PackageManager pm = getPackageManager(); 101 | boolean support = pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); 102 | if (!support) { 103 | Toast.makeText(this, "BLE is not supported", Toast.LENGTH_SHORT).show(); 104 | finish(); 105 | return; 106 | } 107 | BluetoothManager mgr = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 108 | mBluetoothAdapter = mgr.getAdapter(); 109 | if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { 110 | Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 111 | startActivityForResult(enableBtIntent, 1); 112 | } 113 | mBluetoothDeviceList.clear(); 114 | SharedPreferences sp = this.getSharedPreferences(PREFS_NAME, 0); 115 | int ble_state = sp.getInt(BleStateKey, 0); 116 | Log.i(TAG, "read ble state : " + ble_state); 117 | /* 118 | * // if(ANCSGattCallback.BleDisconnect != ble_state){ if( ble_state > -1){ //must be boolean 119 | * auto = sp.getBoolean(BleAutoKey, true); String addr = sp.getString(BleAddrKey, ""); Intent 120 | * intent = new Intent(this, BLEConnect.class); intent.putExtra("addr", addr); 121 | * intent.putExtra("auto", auto); intent.putExtra("state", ble_state); startActivity(intent); 122 | * finish(); return; } 123 | */ 124 | // scan(true); 125 | getListView().setAdapter(mListAdapter); 126 | // stop automatic scan , I try to list my iphone by mac address 127 | // connect is success, but status is 133, keep the code temp. 128 | // BluetoothDevice device = 129 | // BluetoothAdapter.getDefaultAdapter().getRemoteDevice("76:88:CE:4D:3F:AE"); 130 | // mList.add(device); 131 | mListAdapter.notifyDataSetChanged(); 132 | } 133 | 134 | 135 | void scan(final boolean enable) { 136 | if (enable) { 137 | // Stops scanning after a pre-defined scan period. 138 | Log.i(TAG, "start to scan."); 139 | isBLEScaning = true; 140 | mBluetoothAdapter.startLeScan(mLEScanCallback); 141 | buttonScan.setText(R.string.stop_scan); 142 | } else { 143 | if (isBLEScaning) { 144 | mBluetoothAdapter.stopLeScan(mLEScanCallback); 145 | isBLEScaning = false; 146 | buttonScan.setText(R.string.scan); 147 | Log.i(TAG, "stop scan"); 148 | } 149 | } 150 | } 151 | 152 | @Override 153 | protected void onDestroy() { 154 | scan(false); 155 | super.onDestroy(); 156 | } 157 | 158 | @Override 159 | public boolean onOptionsItemSelected(MenuItem item) { 160 | switch (item.getItemId()) { 161 | case R.id.scan: 162 | mBluetoothDeviceList.clear(); 163 | scan(true); 164 | break; 165 | } 166 | return true; 167 | } 168 | 169 | protected void onListItemClick(ListView l, View v, int position, long id) { 170 | BluetoothDevice dev = mBluetoothDeviceList.get(position); 171 | scan(false); 172 | Intent intent = new Intent(this, BLEConnectActivity.class); 173 | intent.putExtra("addr", dev.getAddress()); 174 | intent.putExtra("auto", true); 175 | startActivity(intent); 176 | finish(); 177 | } 178 | 179 | 180 | private PermissionsRequest buildPermissionsRequest() { 181 | return new PermissionsRequest(this) { 182 | // 要請求權限時的callback 183 | @Override 184 | public List getCheckPeremission() { 185 | ArrayList permissionsNeeded = new ArrayList(); 186 | permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION); 187 | 188 | return permissionsNeeded; 189 | } 190 | 191 | // 檢查權限完成,不論是否有取得授權,onCheckPeremissionCompleted一定會執行 192 | @Override 193 | public void onCheckPeremissionCompleted() { 194 | } 195 | }; 196 | } 197 | 198 | @Override 199 | public final void onRequestPermissionsResult(int requestCode, 200 | String permissions[], int[] grantResults) { 201 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 202 | 203 | mPermissionsRequest.onRequestPermissionsResult(requestCode, 204 | permissions, grantResults); 205 | } 206 | 207 | private class BLEDeviceListAdapter extends BaseAdapter{ 208 | 209 | @Override 210 | public View getView(int i, View arg1, ViewGroup arg2) { 211 | if (arg1 == null) { 212 | arg1 = LayoutInflater.from(BLEPeripheralListActivity.this).inflate(R.layout.bledevice_list_item, null); 213 | } 214 | BluetoothDevice dev = mBluetoothDeviceList.get(i); 215 | String name = dev.getName(); 216 | if (TextUtils.isEmpty(name)) { 217 | name = "unknow"; 218 | } 219 | 220 | ((TextView)arg1.findViewById(R.id.device_name)).setText("name="+name); 221 | 222 | String address = dev.getAddress(); 223 | if (TextUtils.isEmpty(address)) { 224 | address = "unknow"; 225 | } 226 | ((TextView)arg1.findViewById(R.id.device_bt_hardware_address)).setText("address="+address); 227 | 228 | return arg1; 229 | } 230 | 231 | @Override 232 | public long getItemId(int arg0) { 233 | return 0; 234 | } 235 | 236 | @Override 237 | public Object getItem(int arg0) { 238 | return null; 239 | } 240 | 241 | @Override 242 | public int getCount() { 243 | return mBluetoothDeviceList.size(); 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /app/src/main/java/com/raytw/android/ancssample/ancsandroidsample/util/PermissionsRequest.java: -------------------------------------------------------------------------------- 1 | package com.raytw.android.ancssample.ancsandroidsample.util; 2 | 3 | import android.app.Activity; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.app.ActivityCompat; 8 | import android.support.v4.content.ContextCompat; 9 | import android.util.Log; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * Created by leeray on 16/1/26. 18 | * 19 | * @version 1.0.0 20 | */ 21 | public abstract class PermissionsRequest { 22 | private final static String TAG = PermissionsRequest.class.getSimpleName(); 23 | ; 24 | private boolean isDebug = false; 25 | 26 | static final int REQUEST_MY_PERMISSIONS_REQUEST_MULTIPLE_PERMISSIONS = 1000; 27 | 28 | private Activity mActivity; 29 | private HashMap mPermissionsRequestResult = new HashMap(); 30 | 31 | public PermissionsRequest(Activity activity) { 32 | 33 | mActivity = activity; 34 | } 35 | 36 | private void log(String msg) { 37 | if (isDebug) { 38 | Log.d(TAG, msg); 39 | } 40 | } 41 | 42 | /** 43 | * 此處需回傳要請求系統允許的權限, 例如: 44 | *

45 | *

 46 |      * {@code
 47 |      *
 48 |      *  public List getCheckPeremission() {
 49 |      *      ArrayList permissionsNeeded = new ArrayList();
 50 |      *      permissionsNeeded.add(Manifest.permission.GET_ACCOUNTS);
 51 |      *      permissionsNeeded.add(Manifest.permission.READ_PHONE_STATE);
 52 |      *      permissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
 53 |      *
 54 |      *      return permissionsNeeded;
 55 |      *  }
 56 |      * 
57 | * 58 | * @return 59 | */ 60 | public abstract List getCheckPeremission(); 61 | 62 | /** 63 | * 檢查權限完成 64 | */ 65 | public abstract void onCheckPeremissionCompleted(); 66 | 67 | public void onShouldShowRequestPermissionRationale( 68 | List permissions, List permissionsRequest) { 69 | // TODO 使用者拒絕彈提示授權dialog 70 | } 71 | 72 | public void onRequestPermissionsResult( 73 | Map permissionsWithGrantResults) { 74 | /** 75 | * TODO 請override此method,並自行決定授權有哪些允許的流程處理, 76 | * 使用者缺定是否允許授權,必須一樣一樣權限取出來判斷請求的權限是否有取得,範例如下: 77 | * if(permissionsWithGrantResults.get(Manifest.permission.GET_ACCOUNTS) 78 | * == PackageManager.PERMISSION_GRANTED){ //有取得”GET_ACCOUNTS”權限 } 79 | */ 80 | } 81 | 82 | /** 83 | * 檢查目前是否有允許取得指定授權 84 | * 85 | * @param activity 86 | * @param permission 87 | * @return 88 | */ 89 | public static boolean isPermissionGranted(@NonNull Activity activity, 90 | @NonNull String permission) { 91 | if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) { 92 | return !ActivityCompat.shouldShowRequestPermissionRationale( 93 | activity, permission); 94 | } else { 95 | return true; 96 | } 97 | } 98 | 99 | /** 100 | * 檢查android 6.0以後手機是否有授權權限給app 101 | * 102 | * @param everytime 是否每次問 note : 但若是使用者勾選”不再提醒”,每次問會失效 103 | */ 104 | public void doCheckPermission(boolean everytime) { 105 | if (Build.VERSION.SDK_INT >= 23) { 106 | List permissionsNeeded = getCheckPeremission(); 107 | List permissionsRequest = new ArrayList(); 108 | List permissionsShowRequest = new ArrayList(); 109 | mPermissionsRequestResult.clear(); 110 | 111 | for (String permission : permissionsNeeded) { 112 | mPermissionsRequestResult.put(permission, 113 | PackageManager.PERMISSION_DENIED); 114 | 115 | if (ContextCompat.checkSelfPermission(mActivity, permission) != PackageManager.PERMISSION_GRANTED) { 116 | 117 | log("檢查是否此app有取得允許permission[" + permission + "][false]"); 118 | 119 | log("檢查是否此app有取得允許shouldShowRequestPermissionRationale[" 120 | + ActivityCompat 121 | .shouldShowRequestPermissionRationale( 122 | mActivity, permission) + "]"); 123 | 124 | if (everytime) { 125 | permissionsRequest.add(permission); 126 | } else { 127 | // 使用者拒絕過的授權,但可再次請求的授權 128 | if (ActivityCompat 129 | .shouldShowRequestPermissionRationale( 130 | mActivity, permission)) { 131 | permissionsShowRequest.add(permission); 132 | } else { 133 | permissionsRequest.add(permission); 134 | } 135 | } 136 | } else { 137 | log("檢查是否此app有取得允許permission[" + permission + "][true]"); 138 | } 139 | } 140 | 141 | log("需要的權限=>" + permissionsNeeded); 142 | log("請求權限=>" + permissionsRequest); 143 | log("已拒絕權限=>" + permissionsShowRequest); 144 | 145 | if (permissionsShowRequest.size() > 0) { 146 | onShouldShowRequestPermissionRationale(permissionsShowRequest, 147 | permissionsRequest); 148 | } 149 | 150 | if (permissionsRequest.size() > 0) { 151 | log("shouldShowRequestPermissionRationale,false"); 152 | // No explanation needed, we can request the permission. 153 | ActivityCompat.requestPermissions(mActivity, permissionsRequest 154 | .toArray(new String[permissionsRequest.size()]), 155 | REQUEST_MY_PERMISSIONS_REQUEST_MULTIPLE_PERMISSIONS); 156 | return; 157 | } 158 | } 159 | onCheckPeremissionCompleted(); 160 | } 161 | 162 | // 請求app權限授權的response 163 | public void onRequestPermissionsResult(int requestCode, 164 | String permissions[], int[] grantResults) { 165 | switch (requestCode) { 166 | case REQUEST_MY_PERMISSIONS_REQUEST_MULTIPLE_PERMISSIONS: { 167 | log("onRequestPermissionsResult,REQUEST_MY_PERMISSIONS_REQUEST_READ_CONTACTS,grantResults.length[" 168 | + grantResults.length + "]"); 169 | 170 | for (int i = 0; i < grantResults.length; i++) { 171 | mPermissionsRequestResult.put(permissions[i], grantResults[i]); 172 | } 173 | log("權限result=>" + mPermissionsRequestResult); 174 | 175 | onRequestPermissionsResult(mPermissionsRequestResult); 176 | onCheckPeremissionCompleted(); 177 | return; 178 | } 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_eztalk_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTW/ANCSAndroidSample/25321e9c20c32096e1dd41d4b8e63194c4971b49/app/src/main/res/drawable-hdpi/btn_eztalk_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_eztalk_b_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTW/ANCSAndroidSample/25321e9c20c32096e1dd41d4b8e63194c4971b49/app/src/main/res/drawable-hdpi/btn_eztalk_b_p.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTW/ANCSAndroidSample/25321e9c20c32096e1dd41d4b8e63194c4971b49/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/notification_contact_click_nsi3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 11 | 12 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/notify_icon_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTW/ANCSAndroidSample/25321e9c20c32096e1dd41d4b8e63194c4971b49/app/src/main/res/drawable-hdpi/notify_icon_default.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTW/ANCSAndroidSample/25321e9c20c32096e1dd41d4b8e63194c4971b49/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/notify_bar_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTW/ANCSAndroidSample/25321e9c20c32096e1dd41d4b8e63194c4971b49/app/src/main/res/drawable-mdpi/notify_bar_background.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RayTW/ANCSAndroidSample/25321e9c20c32096e1dd41d4b8e63194c4971b49/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_devices.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 |