@Override
		public void onCharacteristicChanged(
				@NonNull final BluetoothGatt gatt,
				@NonNull final BluetoothGattCharacteristic characteristic,
				@NonNull final byte[] data) {
			if (isServiceChangedCharacteristic(characteristic)) {
				// Android S added onServiceChanged() callback, which should be called in this
				// situation. Again, this has not been tested.
				if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
					log(Log.INFO, () -> "Service Changed indication received");
					// For older APIs, trigger service discovery.
					// TODO this should be tested. Should services be invalidated?
					// Forbid enqueuing more operations.
					operationInProgress = true;
					// Invalidate all services and characteristics
					manager.onServicesInvalidated();
					onDeviceDisconnected();
					// Clear queues, services are no longer valid.
					emptyTasks(FailCallback.REASON_NULL_ATTRIBUTE);
					serviceDiscoveryRequested = true;
					log(Log.VERBOSE, () -> "Discovering Services...");
					log(Log.DEBUG, () -> "gatt.discoverServices()");
					gatt.discoverServices();
				}
				return;
			}

			final BluetoothGattDescriptor cccd =
					characteristic.getDescriptor(BleManager.CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
			final boolean notifications = cccd == null || cccd.getValue() == null ||
					cccd.getValue().length != 2 || cccd.getValue()[0] == 0x01;

			if (notifications) {
				log(Log.INFO, () -> "Notification received from " +
						characteristic.getUuid() + ", value: " + ParserUtils.parse(data));
				onCharacteristicNotified(gatt, characteristic);
			} else { // indications
				log(Log.INFO, () -> "Indication received from " +
						characteristic.getUuid() + ", value: " + ParserUtils.parse(data));
				onCharacteristicIndicated(gatt, characteristic);
			}
			if (batteryLevelNotificationCallback != null && isBatteryLevelCharacteristic(characteristic)) {
				batteryLevelNotificationCallback.notifyValueChanged(gatt.getDevice(), data);
			}
			// Notify the notification registered listener, if set
			final ValueChangedCallback request = valueChangedCallbacks.get(characteristic);
			if (request != null && request.matches(data)) {
				request.notifyValueChanged(gatt.getDevice(), data);
			}
			// If there is a value change request,
			if (awaitingRequest instanceof final WaitForValueChangedRequest valueChangedRequest
					// registered for this characteristic
					&& awaitingRequest.characteristic == characteristic
					// and didn't have a trigger, or the trigger was started
					// (not necessarily completed)
					&& !awaitingRequest.isTriggerPending()) {
				if (valueChangedRequest.matches(data)) {
					// notify that new data was received.
					valueChangedRequest.notifyValueChanged(gatt.getDevice(), data);

					// If no more data are expected
					if (valueChangedRequest.isComplete()) {
						log(Log.INFO, () -> "Wait for value changed complete");
						// notify success,
						valueChangedRequest.notifySuccess(gatt.getDevice());
						// and proceed to the next request only if the trigger has completed.
						// Otherwise, the next request will be started when the request's callback
						// will be received.
						awaitingRequest = null;
						if (valueChangedRequest.isTriggerCompleteOrNull()) {
							nextRequest(true);
						}
					}
				}
			}
			if (checkCondition()) {
				nextRequest(true);
			}
		}
Callback indicating service changed event is received.
<p>
Receiving this event means that the GATT database is out of sync with the remote device.
<p>
Requires API 31+.
ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java