[DP]343. Integer Break

探讨如何将正整数n拆分成至少两个正整数之和,并最大化这些整数的乘积。通过数学分析得出算法,使用C++实现。适用于范围2至58的整数。

题目:

Given a positive integer n, break it into the sum of at least two positive integers and maximize the product of those integers. Return the maximum product you can get.

For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).

Note: You may assume that n is not less than 2 and not larger than 58.


题目分析:

1、根据题意,我们需要将给出的N(1<N<59)拆分成至少两个数,使其和为N,且要尽量让其乘积最大;

2、首先根据例举一些简单的数的积最大拆分我们可以发现,除了2和3以外(因为至少要拆分为两个数),别的数要使其拆分后积最大,就要将其拆分为2和3组成的和(且尽可能有多的3)。因此算法就变得很简单了,只需要N/3统计能拆分为多少个3,再根据N%3来判断是否能拆成完全的2和3的组合(余数为0则全为3,余数为2则有一个2,余数为1则须减少一个3改为两个2);

代码:

#include<cmath>
class Solution {
public:
    int integerBreak(int n) {
        if(n == 2) return 1;
        if(n == 3) return 2;
        
        int p = n/3;
        if(n % 3 == 0)
            return pow(3, p);
        else if(n % 3 == 1)
            return pow(3, p - 1) * 4;
        else
            return pow(3, p) * 2;
    }
};

3、对上述算法进行理论上的分析:

     首先对于2和3来说,它们符合拆分为尽可能多的3,剩下的为2的算法过程和结论(不考虑必须拆分为至少两个数的限制)。

     对于大于3任意数N而言,假设它已经被拆分为3和2的和的形式,如果有一个更优解(和式包含不为2或3的数N0),那么N0本身也可以被表示为2和3的和式,也就是说这个最优解利用N0替换表示N的2和3的和式中的一部分,已达到更大的乘积,假设N0 = 3m + 2n,那么N0 > 3^m + 2^n即3m + 2n > 3^m + 2^n,显然式子在m, n > 1时不可能成立。所以推翻了又更优解的可能。 

讲解下面的代码: 1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 * Changes from Qualcomm Innovation Center are provided under the following license: 17 * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. 18 * SPDX-License-Identifier: BSD-3-Clause-Clear 19 */ 20 21 /** 22 * Bluetooth A2DP StateMachine. There is one instance per remote device. 23 * - "Disconnected" and "Connected" are steady states. 24 * - "Connecting" and "Disconnecting" are transient states until the 25 * connection / disconnection is completed. 26 * 27 * 28 * (Disconnected) 29 * | ^ 30 * CONNECT | | DISCONNECTED 31 * V | 32 * (Connecting)<--->(Disconnecting) 33 * | ^ 34 * CONNECTED | | DISCONNECT 35 * V | 36 * (Connected) 37 * NOTES: 38 * - If state machine is in "Connecting" state and the remote device sends 39 * DISCONNECT request, the state machine transitions to "Disconnecting" state. 40 * - Similarly, if the state machine is in "Disconnecting" state and the remote device 41 * sends CONNECT request, the state machine transitions to "Connecting" state. 42 * 43 * DISCONNECT 44 * (Connecting) ---------------> (Disconnecting) 45 * <--------------- 46 * CONNECT 47 * 48 */ 49 50 package com.android.bluetooth.a2dp; 51 52 import static android.Manifest.permission.BLUETOOTH_CONNECT; 53 54 import android.bluetooth.BluetoothA2dp; 55 import android.bluetooth.BluetoothCodecConfig; 56 import android.bluetooth.BluetoothCodecStatus; 57 import android.bluetooth.BluetoothDevice; 58 import android.bluetooth.BluetoothProfile; 59 import android.content.Intent; 60 import android.os.Looper; 61 import android.os.Message; 62 import android.util.Log; 63 64 import com.android.bluetooth.BluetoothStatsLog; 65 import com.android.bluetooth.Utils; 66 import com.android.bluetooth.btservice.ProfileService; 67 import com.android.bluetooth.apm.ApmConstIntf; 68 import com.android.bluetooth.apm.DeviceProfileMapIntf; 69 import com.android.bluetooth.apm.MediaAudioIntf; 70 71 import com.android.internal.annotations.VisibleForTesting; 72 import com.android.internal.util.State; 73 import com.android.internal.util.StateMachine; 74 75 import java.io.FileDescriptor; 76 import java.io.PrintWriter; 77 import java.io.StringWriter; 78 import java.util.List; 79 import java.util.Scanner; 80 import java.lang.reflect.Method; 81 import android.os.SystemProperties; 82 import com.android.bluetooth.btservice.AdapterService; 83 84 final class A2dpStateMachine extends StateMachine { 85 private static final boolean DBG = true; 86 private static final String TAG = "A2dpStateMachine"; 87 88 static final int CONNECT = 1; 89 static final int DISCONNECT = 2; 90 @VisibleForTesting 91 static final int STACK_EVENT = 101; 92 private static final int CONNECT_TIMEOUT = 201; 93 94 // NOTE: the value is not "final" - it is modified in the unit tests 95 @VisibleForTesting 96 static int sConnectTimeoutMs = 30000; // 30s 97 98 private Disconnected mDisconnected; 99 private Connecting mConnecting; 100 private Disconnecting mDisconnecting; 101 private Connected mConnected; 102 private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED; 103 private int mLastConnectionState = -1; 104 105 private A2dpService mA2dpService; 106 private A2dpNativeInterface mA2dpNativeInterface; 107 @VisibleForTesting 108 boolean mA2dpOffloadEnabled = false; 109 private final BluetoothDevice mDevice; 110 private boolean mIsPlaying = false; 111 private BluetoothCodecStatus mCodecStatus; 112 private boolean mCodecConfigUpdated = false; 113 114 A2dpStateMachine(BluetoothDevice device, A2dpService a2dpService, 115 A2dpNativeInterface a2dpNativeInterface, Looper looper) { 116 super(TAG, looper); 117 setDbg(DBG); 118 mDevice = device; 119 mCodecConfigUpdated = false; 120 mA2dpService = a2dpService; 121 mA2dpNativeInterface = a2dpNativeInterface; 122 123 mDisconnected = new Disconnected(); 124 mConnecting = new Connecting(); 125 mDisconnecting = new Disconnecting(); 126 mConnected = new Connected(); 127 128 addState(mDisconnected); 129 addState(mConnecting); 130 addState(mDisconnecting); 131 addState(mConnected); 132 mA2dpOffloadEnabled = mA2dpService.mA2dpOffloadEnabled; 133 134 setInitialState(mDisconnected); 135 } 136 137 static A2dpStateMachine make(BluetoothDevice device, A2dpService a2dpService, 138 A2dpNativeInterface a2dpNativeInterface, Looper looper) { 139 Log.i(TAG, "make for device " + device); 140 A2dpStateMachine a2dpSm = new A2dpStateMachine(device, a2dpService, a2dpNativeInterface, 141 looper); 142 a2dpSm.start(); 143 return a2dpSm; 144 } 145 146 public void doQuit() { 147 log("doQuit for device " + mDevice); 148 if (mIsPlaying) { 149 // Stop if auido is still playing 150 log("doQuit: stopped playing " + mDevice); 151 mIsPlaying = false; 152 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice); 153 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING, 154 BluetoothA2dp.STATE_PLAYING); 155 } 156 quitNow(); 157 } 158 159 public void cleanup() { 160 log("cleanup for device " + mDevice); 161 } 162 163 @VisibleForTesting 164 class Disconnected extends State { 165 @Override 166 public void enter() { 167 Message currentMessage = getCurrentMessage(); 168 Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null" 169 : messageWhatToString(currentMessage.what))); 170 mCodecConfigUpdated = false; 171 synchronized (this) { 172 mConnectionState = BluetoothProfile.STATE_DISCONNECTED; 173 } 174 removeDeferredMessages(DISCONNECT); 175 176 if (mLastConnectionState != -1) { 177 // Don't broadcast during startup 178 if (mIsPlaying) { 179 Log.i(TAG, "Disconnected: stopped playing: " + mDevice); 180 mIsPlaying = false; 181 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice); 182 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING, 183 BluetoothA2dp.STATE_PLAYING); 184 } 185 broadcastConnectionState(mConnectionState, mLastConnectionState); 186 AdapterService adapterService = AdapterService.getAdapterService(); 187 if (adapterService.isVendorIntfEnabled() && 188 adapterService.isTwsPlusDevice(mDevice)) { 189 mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_NOT_PLAYING, mDevice); 190 } 191 } 192 } 193 194 @Override 195 public void exit() { 196 Message currentMessage = getCurrentMessage(); 197 log("Exit Disconnected(" + mDevice + "): " + (currentMessage == null ? "null" 198 : messageWhatToString(currentMessage.what))); 199 mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED; 200 } 201 202 @Override 203 public boolean processMessage(Message message) { 204 log("Disconnected process message(" + mDevice + "): " 205 + messageWhatToString(message.what)); 206 207 switch (message.what) { 208 case CONNECT: 209 Log.i(TAG, "Connecting to " + mDevice); 210 if (!mA2dpNativeInterface.connectA2dp(mDevice)) { 211 Log.e(TAG, "Disconnected: error connecting to " + mDevice); 212 break; 213 } 214 if (mA2dpService.okToConnect(mDevice, true)) { 215 transitionTo(mConnecting); 216 } else { 217 // Reject the request and stay in Disconnected state 218 Log.w(TAG, "Outgoing A2DP Connecting request rejected: " + mDevice); 219 } 220 break; 221 case DISCONNECT: 222 Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); 223 break; 224 case STACK_EVENT: 225 A2dpStackEvent event = (A2dpStackEvent) message.obj; 226 log("Disconnected: stack event: " + event); 227 if (!mDevice.equals(event.device)) { 228 Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); 229 } 230 switch (event.type) { 231 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 232 processConnectionEvent(event.valueInt); 233 break; 234 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: 235 processCodecConfigEvent(event.codecStatus); 236 break; 237 default: 238 Log.e(TAG, "Disconnected: ignoring stack event: " + event); 239 break; 240 } 241 break; 242 default: 243 return NOT_HANDLED; 244 } 245 return HANDLED; 246 } 247 248 // in Disconnected state 249 private void processConnectionEvent(int event) { 250 switch (event) { 251 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: 252 Log.w(TAG, "Ignore A2DP DISCONNECTED event: " + mDevice); 253 break; 254 case A2dpStackEvent.CONNECTION_STATE_CONNECTING: 255 if (mA2dpService.okToConnect(mDevice, false)) { 256 Log.i(TAG, "Incoming A2DP Connecting request accepted: " + mDevice); 257 transitionTo(mConnecting); 258 if (mA2dpService.isQtiLeAudioEnabled()) { 259 MediaAudioIntf mMediaAudio = MediaAudioIntf.get(); 260 mMediaAudio.autoConnect(mDevice); 261 } 262 } else { 263 // Reject the connection and stay in Disconnected state itself 264 Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice); 265 mA2dpNativeInterface.disconnectA2dp(mDevice); 266 } 267 break; 268 case A2dpStackEvent.CONNECTION_STATE_CONNECTED: 269 Log.w(TAG, "A2DP Connected from Disconnected state: " + mDevice); 270 if (mA2dpService.okToConnect(mDevice, false)) { 271 Log.i(TAG, "Incoming A2DP Connected request accepted: " + mDevice); 272 transitionTo(mConnected); 273 if (mA2dpService.isQtiLeAudioEnabled()) { 274 MediaAudioIntf mMediaAudio = MediaAudioIntf.get(); 275 mMediaAudio.autoConnect(mDevice); 276 } 277 } else { 278 // Reject the connection and stay in Disconnected state itself 279 Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice); 280 mA2dpNativeInterface.disconnectA2dp(mDevice); 281 } 282 break; 283 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: 284 Log.w(TAG, "Ignore A2DP DISCONNECTING event: " + mDevice); 285 break; 286 default: 287 Log.e(TAG, "Incorrect event: " + event + " device: " + mDevice); 288 break; 289 } 290 } 291 } 292 293 @VisibleForTesting 294 class Connecting extends State { 295 @Override 296 public void enter() { 297 Message currentMessage = getCurrentMessage(); 298 Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null" 299 : messageWhatToString(currentMessage.what))); 300 sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); 301 synchronized (this) { 302 mConnectionState = BluetoothProfile.STATE_CONNECTING; 303 } 304 broadcastConnectionState(mConnectionState, mLastConnectionState); 305 } 306 307 @Override 308 public void exit() { 309 Message currentMessage = getCurrentMessage(); 310 log("Exit Connecting(" + mDevice + "): " + (currentMessage == null ? "null" 311 : messageWhatToString(currentMessage.what))); 312 mLastConnectionState = BluetoothProfile.STATE_CONNECTING; 313 removeMessages(CONNECT_TIMEOUT); 314 } 315 316 @Override 317 public boolean processMessage(Message message) { 318 log("Connecting process message(" + mDevice + "): " 319 + messageWhatToString(message.what)); 320 321 switch (message.what) { 322 case CONNECT: 323 deferMessage(message); 324 break; 325 case CONNECT_TIMEOUT: { 326 Log.w(TAG, "Connecting connection timeout: " + mDevice); 327 mA2dpNativeInterface.disconnectA2dp(mDevice); 328 A2dpStackEvent event = 329 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 330 event.device = mDevice; 331 event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED; 332 sendMessage(STACK_EVENT, event); 333 break; 334 } 335 case DISCONNECT: 336 // Cancel connection 337 Log.i(TAG, "Connecting: connection canceled to " + mDevice); 338 mA2dpNativeInterface.disconnectA2dp(mDevice); 339 transitionTo(mDisconnected); 340 break; 341 case STACK_EVENT: 342 A2dpStackEvent event = (A2dpStackEvent) message.obj; 343 log("Connecting: stack event: " + event); 344 if (!mDevice.equals(event.device)) { 345 Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); 346 } 347 switch (event.type) { 348 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 349 processConnectionEvent(event.valueInt); 350 break; 351 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: 352 processCodecConfigEvent(event.codecStatus); 353 break; 354 case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: 355 break; 356 default: 357 Log.e(TAG, "Connecting: ignoring stack event: " + event); 358 break; 359 } 360 break; 361 default: 362 return NOT_HANDLED; 363 } 364 return HANDLED; 365 } 366 367 // in Connecting state 368 private void processConnectionEvent(int event) { 369 switch (event) { 370 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: 371 Log.w(TAG, "Connecting device disconnected: " + mDevice); 372 transitionTo(mDisconnected); 373 break; 374 case A2dpStackEvent.CONNECTION_STATE_CONNECTED: 375 transitionTo(mConnected); 376 break; 377 case A2dpStackEvent.CONNECTION_STATE_CONNECTING: 378 // Ignored - probably an event that the outgoing connection was initiated 379 break; 380 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: 381 Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice); 382 transitionTo(mDisconnecting); 383 break; 384 default: 385 Log.e(TAG, "Incorrect event: " + event); 386 break; 387 } 388 } 389 } 390 391 @VisibleForTesting 392 class Disconnecting extends State { 393 @Override 394 public void enter() { 395 Message currentMessage = getCurrentMessage(); 396 Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null" 397 : messageWhatToString(currentMessage.what))); 398 mCodecConfigUpdated = false; 399 sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); 400 synchronized (this) { 401 mConnectionState = BluetoothProfile.STATE_DISCONNECTING; 402 } 403 broadcastConnectionState(mConnectionState, mLastConnectionState); 404 } 405 406 @Override 407 public void exit() { 408 Message currentMessage = getCurrentMessage(); 409 log("Exit Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null" 410 : messageWhatToString(currentMessage.what))); 411 mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; 412 removeMessages(CONNECT_TIMEOUT); 413 } 414 415 @Override 416 public boolean processMessage(Message message) { 417 log("Disconnecting process message(" + mDevice + "): " 418 + messageWhatToString(message.what)); 419 420 switch (message.what) { 421 case CONNECT: 422 deferMessage(message); 423 break; 424 case CONNECT_TIMEOUT: { 425 Log.w(TAG, "Disconnecting connection timeout: " + mDevice); 426 mA2dpNativeInterface.disconnectA2dp(mDevice); 427 A2dpStackEvent event = 428 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); 429 event.device = mDevice; 430 event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED; 431 sendMessage(STACK_EVENT, event); 432 break; 433 } 434 case DISCONNECT: 435 deferMessage(message); 436 break; 437 case STACK_EVENT: 438 A2dpStackEvent event = (A2dpStackEvent) message.obj; 439 log("Disconnecting: stack event: " + event); 440 if (!mDevice.equals(event.device)) { 441 Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); 442 } 443 switch (event.type) { 444 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 445 processConnectionEvent(event.valueInt); 446 break; 447 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: 448 processCodecConfigEvent(event.codecStatus); 449 break; 450 case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: 451 default: 452 Log.e(TAG, "Disconnecting: ignoring stack event: " + event); 453 break; 454 } 455 break; 456 default: 457 return NOT_HANDLED; 458 } 459 return HANDLED; 460 } 461 462 // in Disconnecting state 463 private void processConnectionEvent(int event) { 464 switch (event) { 465 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: 466 Log.i(TAG, "Disconnected: " + mDevice); 467 transitionTo(mDisconnected); 468 break; 469 case A2dpStackEvent.CONNECTION_STATE_CONNECTED: 470 if (mA2dpService.okToConnect(mDevice, false)) { 471 Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice); 472 transitionTo(mConnected); 473 if (mA2dpService.isQtiLeAudioEnabled()) { 474 MediaAudioIntf mMediaAudio = MediaAudioIntf.get(); 475 mMediaAudio.autoConnect(mDevice); 476 } 477 } else { 478 // Reject the connection and stay in Disconnecting state 479 Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice); 480 mA2dpNativeInterface.disconnectA2dp(mDevice); 481 } 482 break; 483 case A2dpStackEvent.CONNECTION_STATE_CONNECTING: 484 if (mA2dpService.okToConnect(mDevice, false)) { 485 Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice); 486 transitionTo(mConnecting); 487 if (mA2dpService.isQtiLeAudioEnabled()) { 488 MediaAudioIntf mMediaAudio = MediaAudioIntf.get(); 489 mMediaAudio.autoConnect(mDevice); 490 } 491 } else { 492 // Reject the connection and stay in Disconnecting state 493 Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice); 494 mA2dpNativeInterface.disconnectA2dp(mDevice); 495 } 496 break; 497 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: 498 // We are already disconnecting, do nothing 499 Log.i(TAG, " already disconnecting, do nothing "); 500 break; 501 default: 502 Log.e(TAG, "Incorrect event: " + event); 503 break; 504 } 505 } 506 } 507 508 @VisibleForTesting 509 class Connected extends State { 510 @Override 511 public void enter() { 512 Message currentMessage = getCurrentMessage(); 513 Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null" 514 : messageWhatToString(currentMessage.what))); 515 synchronized (this) { 516 mConnectionState = BluetoothProfile.STATE_CONNECTED; 517 } 518 removeDeferredMessages(CONNECT); 519 DeviceProfileMapIntf dpm = DeviceProfileMapIntf.getDeviceProfileMapInstance(); 520 dpm.profileConnectionUpdate(mDevice, ApmConstIntf.AudioFeatures.MEDIA_AUDIO, 521 ApmConstIntf.AudioProfiles.A2DP, true); 522 // Each time a device connects, we want to re-check if it supports optional 523 // codecs (perhaps it's had a firmware update, etc.) and save that state if 524 // it differs from what we had saved before. 525 mA2dpService.updateOptionalCodecsSupport(mDevice); 526 broadcastConnectionState(mConnectionState, mLastConnectionState); 527 // Upon connected, the audio starts out as stopped 528 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING, 529 BluetoothA2dp.STATE_PLAYING); 530 } 531 532 @Override 533 public void exit() { 534 Message currentMessage = getCurrentMessage(); 535 log("Exit Connected(" + mDevice + "): " + (currentMessage == null ? "null" 536 : messageWhatToString(currentMessage.what))); 537 mLastConnectionState = BluetoothProfile.STATE_CONNECTED; 538 } 539 540 @Override 541 public boolean processMessage(Message message) { 542 log("Connected process message(" + mDevice + "): " + messageWhatToString(message.what)); 543 544 switch (message.what) { 545 case CONNECT: 546 Log.w(TAG, "Connected: CONNECT ignored: " + mDevice); 547 break; 548 case DISCONNECT: { 549 Log.i(TAG, "Disconnecting from " + mDevice); 550 if (!mA2dpNativeInterface.disconnectA2dp(mDevice)) { 551 // If error in the native stack, transition directly to Disconnected state. 552 Log.e(TAG, "Connected: error disconnecting from " + mDevice); 553 transitionTo(mDisconnected); 554 break; 555 } 556 transitionTo(mDisconnecting); 557 } 558 break; 559 case STACK_EVENT: 560 A2dpStackEvent event = (A2dpStackEvent) message.obj; 561 log("Connected: stack event: " + event); 562 if (!mDevice.equals(event.device)) { 563 Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); 564 } 565 switch (event.type) { 566 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 567 processConnectionEvent(event.valueInt); 568 break; 569 case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: 570 processAudioStateEvent(event.valueInt); 571 break; 572 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: 573 processCodecConfigEvent(event.codecStatus); 574 break; 575 default: 576 Log.e(TAG, "Connected: ignoring stack event: " + event); 577 break; 578 } 579 break; 580 default: 581 return NOT_HANDLED; 582 } 583 return HANDLED; 584 } 585 586 // in Connected state 587 private void processConnectionEvent(int event) { 588 switch (event) { 589 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED: 590 Log.i(TAG, "Disconnected from " + mDevice); 591 transitionTo(mDisconnected); 592 break; 593 case A2dpStackEvent.CONNECTION_STATE_CONNECTED: 594 Log.w(TAG, "Ignore A2DP CONNECTED event: " + mDevice); 595 break; 596 case A2dpStackEvent.CONNECTION_STATE_CONNECTING: 597 Log.w(TAG, "Ignore A2DP CONNECTING event: " + mDevice); 598 break; 599 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING: 600 Log.i(TAG, "Disconnecting from " + mDevice); 601 transitionTo(mDisconnecting); 602 break; 603 default: 604 Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event); 605 break; 606 } 607 } 608 609 // in Connected state 610 private void processAudioStateEvent(int state) { 611 Log.i(TAG, "Connected: processAudioStateEvent: state: " + state + "mIsPlaying: " + mIsPlaying); 612 switch (state) { 613 case A2dpStackEvent.AUDIO_STATE_STARTED: 614 synchronized (this) { 615 if (!mIsPlaying) { 616 Log.i(TAG, "Connected: started playing: " + mDevice); 617 mIsPlaying = true; 618 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING, mDevice); 619 broadcastAudioState(BluetoothA2dp.STATE_PLAYING, 620 BluetoothA2dp.STATE_NOT_PLAYING); 621 Log.i(TAG,"state:AUDIO_STATE_STARTED"); 622 } 623 AdapterService adapterService = AdapterService.getAdapterService(); 624 if (adapterService.isVendorIntfEnabled() && 625 adapterService.isTwsPlusDevice(mDevice)) { 626 mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_PLAYING, mDevice); 627 } 628 } 629 break; 630 case A2dpStackEvent.AUDIO_STATE_REMOTE_SUSPEND: 631 case A2dpStackEvent.AUDIO_STATE_STOPPED: 632 synchronized (this) { 633 if (mIsPlaying) { 634 Log.i(TAG, "Connected: stopped playing: " + mDevice); 635 mIsPlaying = false; 636 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice); 637 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING, 638 BluetoothA2dp.STATE_PLAYING); 639 mA2dpService.setGamingMode(mDevice, false); 640 } 641 } 642 break; 643 default: 644 Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state); 645 break; 646 } 647 } 648 } 649 650 int getConnectionState() { 651 return mConnectionState; 652 } 653 654 BluetoothDevice getDevice() { 655 return mDevice; 656 } 657 658 boolean isConnected() { 659 synchronized (this) { 660 return (mConnectionState == BluetoothProfile.STATE_CONNECTED); 661 } 662 } 663 664 boolean isPlaying() { 665 synchronized (this) { 666 return mIsPlaying; 667 } 668 } 669 670 BluetoothCodecStatus getCodecStatus() { 671 synchronized (this) { 672 return mCodecStatus; 673 } 674 } 675 676 // NOTE: This event is processed in any state 677 @VisibleForTesting 678 void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) { 679 BluetoothCodecConfig prevCodecConfig = null; 680 BluetoothCodecStatus prevCodecStatus = mCodecStatus; 681 682 int new_codec_type = newCodecStatus.getCodecConfig().getCodecType(); 683 684 // Split A2dp will be enabled by default 685 boolean isSplitA2dpEnabled = true; 686 AdapterService adapterService = AdapterService.getAdapterService(); 687 Object objStreamAudioService = null; 688 689 if (adapterService != null){ 690 isSplitA2dpEnabled = adapterService.isSplitA2dpEnabled(); 691 Log.v(TAG,"isSplitA2dpEnabled: " + isSplitA2dpEnabled); 692 } else { 693 Log.e(TAG,"adapterService is null"); 694 } 695 696 Log.w(TAG,"processCodecConfigEvent: new_codec_type = " + new_codec_type); 697 698 if (isSplitA2dpEnabled) { 699 if (new_codec_type == BluetoothCodecConfig.SOURCE_QVA_CODEC_TYPE_MAX) { 700 if (adapterService.isVendorIntfEnabled() && 701 adapterService.isTwsPlusDevice(mDevice)) { 702 Log.d(TAG,"TWSP device streaming,not calling reconfig"); 703 mCodecStatus = newCodecStatus; 704 return; 705 } 706 mA2dpService.broadcastReconfigureA2dp(); 707 Log.w(TAG,"Split A2dp enabled rcfg send to Audio for codec max"); 708 return; 709 } 710 } 711 synchronized (this) { 712 if (mCodecStatus != null) { 713 prevCodecConfig = mCodecStatus.getCodecConfig(); 714 } 715 mCodecStatus = newCodecStatus; 716 } 717 if (DBG) { 718 Log.d(TAG, "A2DP Codec Config: " + prevCodecConfig + "->" 719 + newCodecStatus.getCodecConfig()); 720 for (BluetoothCodecConfig codecConfig : 721 newCodecStatus.getCodecsLocalCapabilities()) { 722 Log.d(TAG, "A2DP Codec Local Capability: " + codecConfig); 723 } 724 for (BluetoothCodecConfig codecConfig : 725 newCodecStatus.getCodecsSelectableCapabilities()) { 726 Log.d(TAG, "A2DP Codec Selectable Capability: " + codecConfig); 727 } 728 } 729 730 if (isConnected() && !sameSelectableCodec(prevCodecStatus, mCodecStatus)) { 731 // Remote selectable codec could be changed if codec config changed 732 // in connected state, we need to re-check optional codec status 733 // for this codec change event. 734 mA2dpService.updateOptionalCodecsSupport(mDevice); 735 } 736 Log.d(TAG, " mA2dpOffloadEnabled: " + mA2dpOffloadEnabled); 737 if (mA2dpOffloadEnabled) { 738 boolean update = false; 739 BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig(); 740 if ((prevCodecConfig != null) 741 && (prevCodecConfig.getCodecType() != newCodecConfig.getCodecType())) { 742 Log.d(TAG, " previous codec is different from new codec "); 743 update = true; 744 } else if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig)) { 745 Log.d(TAG, " codec config parameters mismatched from previous config "); 746 update = true; 747 } else if ((newCodecConfig.getCodecType() 748 == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) 749 && (prevCodecConfig != null) 750 && (prevCodecConfig.getCodecSpecific1() 751 != newCodecConfig.getCodecSpecific1())) { 752 Log.d(TAG, "LDAC: codec config parameters mismatched from previous config "); 753 update = true; 754 } else if (prevCodecStatus != null && 755 newCodecStatus.getCodecsSelectableCapabilities().size() != 756 prevCodecStatus.getCodecsSelectableCapabilities().size()){ 757 Log.d(TAG, " codec selectable caps mismatched from previous config "); 758 update = true; 759 } else if (!mCodecConfigUpdated) { 760 Log.d(TAG, " mCodecConfigUpdated is false, codecConfigUpdated is required"); 761 update = true; 762 } 763 764 if ((newCodecConfig.getCodecType() 765 == BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_ADAPTIVE)) { 766 Log.d(TAG, "processCodecConfigEvent, APTX ADAPTIVE: reset Low Latency mode "); 767 try { 768 Class streamAudioService = Class.forName("com.android.bluetooth.apm.StreamAudioService"); 769 Method method = streamAudioService.getDeclaredMethod("getStreamAudioService"); 770 objStreamAudioService = method.invoke(null); 771 if (objStreamAudioService != null) { 772 Log.d(TAG, " processCodecConfigEvent, objStreamAudioService not null:"); 773 } else { 774 Log.d(TAG, " processCodecConfigEvent, objStreamAudioService is null:"); 775 } 776 } catch (Exception ex) { 777 Log.w(TAG, ex); 778 } 779 try { 780 Class streamAudioService = 781 Class.forName("com.android.bluetooth.apm.StreamAudioService"); 782 Method method = streamAudioService.getDeclaredMethod("resetLowLatencyMode"); 783 if (objStreamAudioService != null) { 784 Log.d(TAG, " processCodecConfigEvent, invoke resetLowLatencyMode "); 785 method.invoke(objStreamAudioService); 786 } 787 } catch (Exception ex) { 788 Log.w(TAG, ex); 789 } 790 } 791 Log.d(TAG, " update: " + update); 792 if (update) { 793 mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false); 794 mCodecConfigUpdated = true; 795 } 796 return; 797 } 798 799 Log.d(TAG, " isSplitA2dpEnabled: " + isSplitA2dpEnabled); 800 if (!isSplitA2dpEnabled) { 801 boolean isUpdateRequired = false; 802 if ((prevCodecConfig != null) && (prevCodecConfig.getCodecType() != new_codec_type)) { 803 Log.d(TAG, "previous codec is differs from new codec"); 804 isUpdateRequired = true; 805 } else if (!newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig)) { 806 Log.d(TAG, "codec config parameters mismatched with previous config: "); 807 isUpdateRequired = true; 808 } else if ((newCodecStatus.getCodecConfig().getCodecType() 809 == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) 810 && (prevCodecConfig != null) 811 && (prevCodecConfig.getCodecSpecific1() 812 != newCodecStatus.getCodecConfig().getCodecSpecific1())) { 813 Log.d(TAG, "LDAC: codec config parameters mismatched with previous config: "); 814 isUpdateRequired = true; 815 } else if(!mCodecConfigUpdated) { 816 Log.d(TAG, " mCodecConfigUpdated is false, codecConfigUpdated is required "); 817 isUpdateRequired = true; 818 } 819 Log.d(TAG, "isUpdateRequired: " + isUpdateRequired); 820 //update MM only when previous and current codec config has been changed 821 // OR reconnection has happened 822 if (isUpdateRequired) { 823 mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false); 824 mCodecConfigUpdated = true; 825 } 826 } 827 } 828 829 // This method does not check for error conditon (newState == prevState) 830 private void broadcastConnectionState(int newState, int prevState) { 831 log("Connection state " + mDevice + ": " + profileStateToString(prevState) 832 + "->" + profileStateToString(newState)); 833 834 if(mA2dpService.isQtiLeAudioEnabled() || ApmConstIntf.getAospLeaEnabled()) { 835 mA2dpService.updateConnState(mDevice, newState); 836 } 837 838 if (!mA2dpService.isQtiLeAudioEnabled()) { 839 Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 840 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 841 intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); 842 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 843 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 844 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 845 mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT, 846 Utils.getTempAllowlistBroadcastOptions()); 847 } 848 } 849 850 private void broadcastAudioState(int newState, int prevState) { 851 log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState) 852 + "->" + audioStateToString(newState)); 853 854 mA2dpService.updateStreamState(mDevice, newState); 855 856 if(mA2dpService.isQtiLeAudioEnabled()) { 857 return; 858 } 859 860 BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_A2DP_PLAYBACK_STATE_CHANGED, newState); 861 Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); 862 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 863 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 864 intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); 865 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 866 mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT, 867 Utils.getTempAllowlistBroadcastOptions()); 868 } 869 870 @Override 871 protected String getLogRecString(Message msg) { 872 StringBuilder builder = new StringBuilder(); 873 builder.append(messageWhatToString(msg.what)); 874 builder.append(": "); 875 builder.append("arg1=") 876 .append(msg.arg1) 877 .append(", arg2=") 878 .append(msg.arg2) 879 .append(", obj=") 880 .append(msg.obj); 881 return builder.toString(); 882 } 883 884 private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus, 885 BluetoothCodecStatus newCodecStatus) { 886 if (prevCodecStatus == null || newCodecStatus == null) { 887 return false; 888 } 889 List<BluetoothCodecConfig> c1 = prevCodecStatus.getCodecsSelectableCapabilities(); 890 List<BluetoothCodecConfig> c2 = newCodecStatus.getCodecsSelectableCapabilities(); 891 if (c1 == null) { 892 return (c2 == null); 893 } 894 if (c2 == null) { 895 return false; 896 } 897 if (c1.size() != c2.size()) { 898 return false; 899 } 900 return c1.containsAll(c2); 901 } 902 903 private static String messageWhatToString(int what) { 904 switch (what) { 905 case CONNECT: 906 return "CONNECT"; 907 case DISCONNECT: 908 return "DISCONNECT"; 909 case STACK_EVENT: 910 return "STACK_EVENT"; 911 case CONNECT_TIMEOUT: 912 return "CONNECT_TIMEOUT"; 913 default: 914 break; 915 } 916 return Integer.toString(what); 917 } 918 919 private static String profileStateToString(int state) { 920 switch (state) { 921 case BluetoothProfile.STATE_DISCONNECTED: 922 return "DISCONNECTED"; 923 case BluetoothProfile.STATE_CONNECTING: 924 return "CONNECTING"; 925 case BluetoothProfile.STATE_CONNECTED: 926 return "CONNECTED"; 927 case BluetoothProfile.STATE_DISCONNECTING: 928 return "DISCONNECTING"; 929 default: 930 break; 931 } 932 return Integer.toString(state); 933 } 934 935 private static String audioStateToString(int state) { 936 switch (state) { 937 case BluetoothA2dp.STATE_PLAYING: 938 return "PLAYING"; 939 case BluetoothA2dp.STATE_NOT_PLAYING: 940 return "NOT_PLAYING"; 941 default: 942 break; 943 } 944 return Integer.toString(state); 945 } 946 947 public void dump(StringBuilder sb) { 948 ProfileService.println(sb, "mDevice: " + mDevice); 949 ProfileService.println(sb, " StateMachine: " + this.toString()); 950 ProfileService.println(sb, " mIsPlaying: " + mIsPlaying); 951 synchronized (this) { 952 if (mCodecStatus != null) { 953 ProfileService.println(sb, " mCodecConfig: " + mCodecStatus.getCodecConfig()); 954 } 955 } 956 // Dump the state machine logs 957 StringWriter stringWriter = new StringWriter(); 958 PrintWriter printWriter = new PrintWriter(stringWriter); 959 super.dump(new FileDescriptor(), printWriter, new String[]{}); 960 printWriter.flush(); 961 stringWriter.flush(); 962 ProfileService.println(sb, " StateMachineLog:"); 963 Scanner scanner = new Scanner(stringWriter.toString()); 964 while (scanner.hasNextLine()) { 965 String line = scanner.nextLine(); 966 ProfileService.println(sb, " " + line); 967 } 968 scanner.close(); 969 } 970 971 @Override 972 protected void log(String msg) { 973 if (DBG) { 974 super.log(msg); 975 } 976 } 977 }
08-30
所给引用中未提及关于Android蓝牙A2DP状态机的Java代码相关内容,所以无法依据引用给出其详细讲解。不过,一般来说,Android蓝牙A2DP状态机的Java代码主要涉及到蓝牙权限配置、连接管理、状态监听等方面。 以下是一个简单的示例代码及其讲解: ```java import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private BluetoothAdapter bluetoothAdapter; private BluetoothDevice targetDevice; private BluetoothProfile.ServiceListener a2dpServiceListener; private BluetoothProfile a2dpProfile; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 获取蓝牙适配器 bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // 注册A2DP状态变化的广播接收器 IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); registerReceiver(a2dpReceiver, filter); // 获取目标蓝牙设备 targetDevice = bluetoothAdapter.getRemoteDevice("XX:XX:XX:XX:XX:XX"); // 替换为实际的蓝牙设备地址 // 监听A2DP服务连接状态 a2dpServiceListener = new BluetoothProfile.ServiceListener() { @Override public void onServiceConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.A2DP) { a2dpProfile = proxy; // 尝试连接到目标设备 a2dpProfile.connect(targetDevice); } } @Override public void onServiceDisconnected(int profile) { if (profile == BluetoothProfile.A2DP) { a2dpProfile = null; } } }; // 绑定A2DP服务 bluetoothAdapter.getProfileProxy(this, a2dpServiceListener, BluetoothProfile.A2DP); } private final BroadcastReceiver a2dpReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); switch (state) { case BluetoothProfile.STATE_CONNECTED: // 设备已连接 break; case BluetoothProfile.STATE_CONNECTING: // 设备正在连接 break; case BluetoothProfile.STATE_DISCONNECTING: // 设备正在断开连接 break; case BluetoothProfile.STATE_DISCONNECTED: // 设备已断开连接 break; } } } }; @Override protected void onDestroy() { super.onDestroy(); // 注销广播接收器 unregisterReceiver(a2dpReceiver); // 关闭A2DP服务代理 if (a2dpProfile != null) { bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, a2dpProfile); } } } ``` ### 代码讲解: 1. **获取蓝牙适配器**:通过`BluetoothAdapter.getDefaultAdapter()`获取系统的蓝牙适配器,用于后续的蓝牙操作。 2. **注册广播接收器**:注册一个广播接收器来监听A2DP连接状态的变化,当连接状态改变时,会触发`onReceive`方法。 3. **获取目标设备**:使用`getRemoteDevice`方法根据设备的MAC地址获取目标蓝牙设备。 4. **监听A2DP服务连接状态**:实现`BluetoothProfile.ServiceListener`接口,当A2DP服务连接成功时,尝试连接到目标设备;当服务断开时,将`a2dpProfile`置为`null`。 5. **绑定A2DP服务**:调用`getProfileProxy`方法绑定A2DP服务,当服务连接成功时会回调`onServiceConnected`方法。 6. **处理A2DP连接状态变化**:在广播接收器的`onReceive`方法中,根据不同的连接状态进行相应的处理。 7. **资源释放**:在`onDestroy`方法中,注销广播接收器并关闭A2DP服务代理,避免资源泄漏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值