
# 代码示例
以下代码仅为示例之一， 用户也可通过调用其他API方法实现获取设备的device_token接口，可参考[租户实例串流前获取设备的device_token](https://support.huaweicloud.com/api-koophone/GetDeviceToken.html)，用于串流前身份鉴权。
#### 工程目录结构代码示例
工程目录结构代码示例最小化实现了云手机串流、断流的能力。
```
CloudPhoneController/
├── app/                        # 应用主模块
│   ├── libs/                   # 存放第三方库文件（如SDK的AAR文件）
│   │   └── cloudphone-sdk.aar  # 云手机SDK库文件
│   ├── src/                    # 源代码目录
│   │   ├── main/               # 主代码目录
│   │   │   ├── java/           # Java源代码目录
│   │   │   │   ├── com/
│   │   │   │   │   └── example/
│   │   │   │   │       └── cloudphonecontroller/
│   │   │   │   │           ├── MainActivity.java        # 主Activity，控制云手机连接/断开
│   │   │   │   │           ├── callback/                 # 回调处理目录
│   │   │   │   │           │   └── CloudPhoneCallBack.java # SDK回调处理器
│   │   │   │   │           ├── constant/                 # 常量定义目录
│   │   │   │   │           │   └── Constant.java         # 应用常量定义
│   │   │   │   │           ├── config/                 # 配置定义目录
│   │   │   │   │           │   └── Config.java         # 配置文件
│   │   │   │   │           ├── model/                    # 数据模型目录
│   │   │   │   │           │   ├── request/              # 请求模型
│   │   │   │   │           │   │   └── AuthRequest.java  # IAM鉴权请求模型
│   │   │   │   │           │   └── response/             # 响应模型
│   │   │   │   │           │       ├── AuthResponse.java # IAM鉴权响应模型
│   │   │   │   │           │       └── ApiResponse.java  # 业务接口响应模型
│   │   │   │   │           ├── network/                  # 网络请求目录
│   │   │   │   │           │   ├── ApiService.java       # 网络接口定义
│   │   │   │   │           │   ├── TokenManager.java     # Token管理器
│   │   │   │   │           │   └── service/              # 服务管理
│   │   │   │   │           │       ├── IAMServiceManager.java # IAM服务管理
│   │   │   │   │           │       └── KooPhoneServiceManager.java # 云手机服务管理
│   │   │   │   │           └── util/                     # 工具类目录
│   │   │   │   │               └── DataSubscriber.java   # 数据订阅者工具
│   │   │   │   │               └──DefaultSubscriber.java   # 数据订阅者抽象类
│   │   │   │   │           └── base/
│   │   │   │   │           │   └──ResponseBean.java   # 统一接口返回数据封装类
│   │   │   │   │           │   └──BaseEntity.java   # 公共基类
│   │   │   ├── res/            # 资源文件目录
│   │   │   │   ├── layout/      # 布局文件
│   │   │   │   │   ├── activity_main.xml  # 主界面布局
│   │   │   │   └── values/      # 资源值
│   │   │   │       └── strings.xml # 字符串资源
│   │   │   └── AndroidManifest.xml # Android应用清单文件
│   │   └── build.gradle        # 模块级构建配置
│   └── build.gradle            # 应用级构建配置
└── gradle.properties           # Gradle属性配置
```
关键文件实现代码示例：
- 模块级构建配置app/build.gradle代码示例：
  ```
  apply plugin: 'com.android.application'
  android {
      compileSdkVersion 34
      buildToolsVersion "30.0.0"
      
      buildFeatures {        
           buildConfig true    
      }
      defaultConfig {
          applicationId "com.example.cloudphonecontroller"
          minSdkVersion 28
          targetSdkVersion 34
          versionCode 1
          versionName "1.0"
          renderscriptTargetApi 21
          renderscriptSupportModeEnabled true
          testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
          // 配置BuildConfig字段
          buildConfigField "String", "INSTANCE_ID", "\"your_instance_id\""
          buildConfigField "String", "USER_ID", "\"43740902807\""
          buildConfigField "int", "SCREEN_WIDTH", "1920"
          buildConfigField "int", "SCREEN_HEIGHT", "1080"
          buildConfigField "int", "AUDIO_SAMPLE_RATE", "48000"
          buildConfigField "int", "AUDIO_CHANNEL_COUNT", "2"
      }
      buildTypes {
          release {
              minifyEnabled false
              proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
          }
      }
      compileOptions {        
              sourceCompatibility JavaVersion.VERSION_17         
              targetCompatibility JavaVersion.VERSION_17     
      }
      buildFeatures {        
              viewBinding true    
      }
      sourceSets {
          getByName("main") {
              jniLibs.srcDirs("libs")
          }
      }
  }
  ext {
      versions = [appcompat       : "1.7.1",
                  activity        : "1.9.3",
                  constraintlayout: "2.2.1",
                  material        : "1.7.0",
                  preference      : "1.1.1",
                  androidx_test   : "1.2.0",
                  junit           : "1.1.1",
                  espresso        : "3.2.0",
                  mockito         : "5.14.2",]
  }
  dependencies {
      implementation platform("org.jetbrains.kotlin:kotlin-bom:1.8.10")
      implementation fileTree(dir: 'libs', include: ['*.aar'])
      
      implementation "org.jetbrains.kotlin:kotlin-stdlib"
      implementation("androidx.appcompat:appcompat:${versions.appcompat}")
      implementation "com.google.android.material:material:${versions.material}"
      implementation "androidx.activity:activity:${versions.activity}" 
      implementation "androidx.constraintlayout:constraintlayout:${versions.constraintlayout}"
      // 测试相关
      testImplementation "junit:junit:4.13.2"
      androidTestImplementation "androidx.test.ext:junit:${versions.junit}"
      androidTestImplementation "androidx.test.espresso:espresso-core:${versions.espresso}"
      
      implementation "com.squareup.retrofit2:retrofit:2.9.0"
      implementation "com.squareup.retrofit2:converter-gson:2.9.0"
      implementation "com.squareup.retrofit2:adapter-rxjava2:2.9.0"
      implementation "io.reactivex.rxjava2:rxjava:2.2.21"
      implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
      // 其他依赖
      implementation "com.fasterxml.jackson.core:jackson-databind:2.12.6"
      implementation 'org.greenrobot:eventbus:3.3.1'
      implementation "androidx.window:window:1.2.0"
      implementation "androidx.window:window-java:1.2.0" 
      // Lombok
      compileOnly "org.projectlombok:lombok:1.18.30"
      annotationProcessor "org.projectlombok:lombok:1.18.30"
      // Glide
      implementation "com.github.bumptech.glide:glide:4.16.0"
      annotationProcessor "com.github.bumptech.glide:compiler:4.16.0"
      implementation "com.github.bumptech.glide:okhttp3-integration:4.12.0"
      implementation "com.squareup.okhttp3:logging-interceptor:4.10.0"
      implementation "androidx.startup:startup-runtime:1.1.1"
  }
  configurations.all {
      resolutionStrategy {
          force "com.google.android.material:material:1.6.1"
          force "androidx.appcompat:appcompat:1.6.1"
      }
  }
  ```
  
- Android应用清单文件AndroidManifest.xml代码示例：
  ```
  <?xml version="1.0" encoding="utf-8"?>
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools">
      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
      <uses-permission android:name="android.permission.RECORD_AUDIO" />
      <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
      <uses-permission
          android:name="android.permission.QUERY_ALL_PACKAGES"
          tools:ignore="QueryAllPackagesPermission" />
      <application
          android:allowBackup="true"
          android:icon="@mipmap/ic_launcher"
          android:label="@string/app_name"
          android:roundIcon="@mipmap/ic_launcher_round"
          android:supportsRtl="true"
          android:theme="@style/Theme.CloudPhoneController"
          android:appComponentFactory="androidx.core.app.CoreComponentFactory"
          tools:replace="android:appComponentFactory,android:allowBackup">
          <activity
              android:name=".MainActivity"
              android:configChanges="orientation|screenSize"
              android:screenOrientation="portrait"
              android:exported="true">
              <intent-filter>
                  <action android:name="android.intent.action.MAIN" />
                  <category android:name="android.intent.category.LAUNCHER" />
              </intent-filter>
          </activity>
      </application>
  </manifest>
  ```
  
- 主界面布局activity_main.xml代码示例：
  ```
  <?xml version="1.0" encoding="utf-8"?>
  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
      <!-- 云手机画面容器 -->
      <FrameLayout
          android:id="@+id/containerf"
          android:layout_width="match_parent"
          android:layout_height="match_parent" />
      <!-- 未连接时的遮罩层 -->
      <View
          android:id="@+id/black_overlay"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:background="#FF000000"
          android:alpha="1" />
      <!-- 控制按钮 -->
      <LinearLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_alignParentBottom="true"
          android:orientation="horizontal"
          android:padding="16dp"
          android:gravity="center">
          <Button
              android:id="@+id/btn_connect"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="连接云手机"
              android:textAllCaps="false"
              android:paddingHorizontal="24dp"
              android:paddingVertical="12dp"/>
          <Button
              android:id="@+id/btn_disconnect"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="断开连接"
              android:textAllCaps="false"
              android:paddingHorizontal="24dp"
              android:paddingVertical="12dp"
              android:layout_marginStart="16dp"/>
      </LinearLayout>
      <!-- 状态显示 -->
      <TextView
          android:id="@+id/tv_status"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_centerInParent="true"
          android:text="状态：未连接"
          android:textColor="@android:color/white"
          android:textSize="18sp" />
  </RelativeLayout>
  ```
  
- 主Activity，控制云手机连接/断开MainActivity.java代码示例：
  ```
  package com.example.cloudphonecontroller;
  import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_ADDRESS;
  import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_PORT;
  import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_TOKEN;
  import static com.nbc.acsdk.adapter.IPlayer.HMTP_PLAYER;
  import android.os.Bundle;
  import android.text.TextUtils;
  import android.view.View;
  import android.widget.Button;
  import android.widget.TextView;
  import android.widget.Toast;
  import androidx.appcompat.app.AppCompatActivity;
  import com.cloudphone.client.api.CloudPhoneClient;
  import com.example.cloudphonecontroller.callback.CloudPhoneCallBack;
  import com.example.cloudphonecontroller.config.Config;
  import com.example.cloudphonecontroller.constant.Constant;
  import com.example.cloudphonecontroller.model.response.ApiResponse;
  import com.example.cloudphonecontroller.network.ApiService;
  import com.example.cloudphonecontroller.network.KooPhoneServiceManager;
  import com.example.cloudphonecontroller.network.TokenManager;
  import com.example.cloudphonecontroller.util.DataSusbcriber;
  import com.google.gson.Gson;
  import com.nbc.acsdk.widget.PlayerFragment;
  import com.nbc.utils.Log;
  import java.io.File;
  import io.reactivex.Observable;
  import io.reactivex.android.schedulers.AndroidSchedulers;
  import io.reactivex.schedulers.Schedulers;
  public class MainActivity extends AppCompatActivity {
      private static Gson gson;
      private View blackOverlay;
      private TextView tvStatus;
      private Button btnConnect, btnDisconnect;
      private PlayerFragment mFragment;
      private boolean isConnected = false;
      private static final String TAG = "MainActivity";
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          // 初始化SDK
          initCloudPhoneSDK();
          Log.info(TAG,"从这里开始 MainActivity onCreate");
          // 初始化UI
          initUI();
      }
      private void initCloudPhoneSDK() {
          // 初始化SDK
          CloudPhoneClient.init(this, null, true);
          // 设置播放渲染模式
          CloudPhoneClient.playerForceTextureView(false);
      }
      private void initUI() {
          //设置页面基本控制组件
          blackOverlay = findViewById(R.id.black_overlay);
          tvStatus = findViewById(R.id.tv_status);
          btnConnect = findViewById(R.id.btn_connect);
          btnDisconnect = findViewById(R.id.btn_disconnect);
          // 初始化PlayerFragment
          if (mFragment == null) {
              mFragment = new PlayerFragment();
              getFragmentManager()
                      .beginTransaction()
                      .add(R.id.containerf, mFragment, "player_fragment")
                      .commit();
              CloudPhoneClient.bindFragment(mFragment);
          }
          // 设置按钮监听
          btnConnect.setOnClickListener(v -> connectCloudPhone());
          btnDisconnect.setOnClickListener(v -> disconnectCloudPhone());
      }
      // 连接云手机功能
      private void connectCloudPhone() {
          // 检查连接状态
          if (isConnected) {
              showToast("已经连接");
              return;
          }
          tvStatus.setText("状态：正在获取鉴权信息...");
          // 在子线程中进行网络请求
          new Thread(() -> {
              try {
                  // 1.获取IAM Token
                  String token = TokenManager.fetchToken();
                  Log.info(TAG,"get token is : " + token);
                  if(Constant.INSTANCE_ID.isEmpty()){
                      this.runOnUiThread(() -> Toast.makeText(getApplicationContext(), "请在gradle.properties里填入 INSTANCE_ID", Toast.LENGTH_LONG).show());
                      return;
                  }
                  // 2.使用IAM Token获取设备连接信息
                  Observable<ApiResponse> deviceInfo = KooPhoneServiceManager
                          .createService(ApiService.class, token)
                          .getDeviceInfo(Constant.INSTANCE_ID);
                  // 3.切换到主线程处理结果
                  runOnUiThread(() -> {
                      deviceInfo.subscribeOn(Schedulers.io())
                              .observeOn(AndroidSchedulers.mainThread())
                              .subscribe(new DataSubscriber<ApiResponse>() {
                                  @Override
                                  public void onNext(ApiResponse response) {
                                      Log.info(TAG,"ApiResponse " + response.toString());
                                      if (response == null || response.getData() == null ||
                                              response.getData().getDevice_token() == null) {
                                          tvStatus.setText("状态：鉴权失败");
                                          showToast("获取的Token为null，请检查实例状态");
                                          return;
                                      }
                                      // 4.启动串流
                                      startStream(response);
                                      tvStatus.setText("状态：已连接");
                                      isConnected = true;
                                      blackOverlay.setVisibility(View.GONE);
                                      tvStatus.setVisibility(View.GONE);
                                  }
                                  @Override
                                  public void onError(Throwable e) {
                                      tvStatus.setText("状态：鉴权失败");
                                      showToast("获取Token失败: " + e.getMessage());
                                      Log.info(TAG,"获取Token失败: " + e.getMessage());
                                  }
                              });
                  });
              } catch (Exception e) {
                  runOnUiThread(() -> {
                      tvStatus.setText("状态：连接失败");
                      showToast("连接异常: " + e.getMessage());
                  });
              }
          }).start();
      }
      private void startStream(ApiResponse response) {
          Bundle bundle = createBundle(response);
          CloudPhoneCallBack callBack = CloudPhoneCallBack.getInstance();
          //云手机蓝牙模块初始化
          EmulationTransport.init(this);
          // 设置回调
          CloudPhoneClient.setPlayerCallback(callBack);
          CloudPhoneClient.setPlayerType(HMTP_PLAYER);
          // 启动串流
          CloudPhoneClient.start(mFragment, bundle);
      }
      private Bundle createBundle(ApiResponse authResponse)  {
          Bundle bundle = new Bundle();
          if (authResponse != null && null != authResponse.getData()) {
              ApiResponse.Data data = authResponse.getData();
              // 会话Token
              bundle.putString(LAUNCH_KEY_TOKEN, TextUtils.isEmpty(data.getDevice_token()) ? "" : data.getDevice_token());
              ApiResponse.Data.Resource resource = data.getResource();
              if (null != resource) {
                  ApiResponse.Data.Resource.Sdk sdk = resource.getSdk();
                  if (null != sdk) {
                      ApiResponse.Data.Resource.Sdk.External external = sdk.getExternal();
                      if (null != external) {
                          //服务地址
                          bundle.putString(LAUNCH_KEY_ADDRESS, external.getAddress());
                          //服务端口
                          bundle.putInt(LAUNCH_KEY_PORT, external.getAport());
                      }
                  }
              }
          }
          // 用户标识
          bundle.putString("userId", Config.USER_ID);
          // 自适应分辨率
          bundle.putBoolean("free_aspect", true);
          // 目标分辨率
          bundle.putInt("screen_width", Config.SCREEN_WIDTH);//任意分辨率宽
          bundle.putInt("screen_height", Config.SCREEN_HEIGHT);//任意分辨率高
          return bundle;
      }
      // 断开云手机连接
      private void disconnectCloudPhone() {
          if (!isConnected) {
              showToast("当前未连接");
              return;
          }
          // 停止串流
          CloudPhoneClient.stop();
          isConnected = false;
          tvStatus.setText("状态：未连接");
          showToast("已断开连接");
          blackOverlay.setVisibility(View.VISIBLE); // 显示黑色遮罩
          tvStatus.setVisibility(View.VISIBLE);
      }
      private void showToast(String message) {
          Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
      }
  }
  ```
  

- SDK回调处理器CloudPhoneCallBack.java代码示例：
  ```
  package com.example.cloudphonecontroller.callback;
  import com.cloudphone.client.api.CloudPhoneClient;
  public class CloudPhoneCallBack implements CloudPhoneClient.Callback, CloudPhoneClient.Callback.AppCallback {
      private static volatile CloudPhoneCallBack instance;
      private CloudPhoneCallBack() {}
      public static CloudPhoneCallBack getInstance() {
          if (instance == null) {
              synchronized (CloudPhoneCallBack.class) {
                  if (instance == null) {
                      instance = new CloudPhoneCallBack();
                  }
              }
          }
          return instance;
      }
      @Override
      public void onSuccess() {
          // 串流建立成功
         EmulationTransport.getInstance().initTransport();
      }
      @Override
      public void onFailure(int code, String msg) {
          // 串流建立失败
      }
  }
  ```
  
- 应用常量定义Constant.java代码示例：
  ```
  package com.example.cloudphonecontroller.constant;
  import com.example.cloudphonecontroller.BuildConfig;
  public class Constant {
      public static final String INSTANCE_ID = BuildConfig.INSTANCE_ID;
      public static final String USER_ID = BuildConfig.USER_ID;
      // IAM鉴权信息
      public static final String IAM_BASE_URL = "https://your-iam-endpoint.com/";
      public static final String USER_NAME = "your_username";
      public static final String USER_PASSWORD = "your_password";
      public static final String DOMAIN_NAME = "your_domain";
      public static final String PROJECT_NAME = "your_project";
      // 业务接口地址
      public static final String API_BASE_URL = "https://your-api-endpoint.com/";
  }
  ```
  
- Token管理器TokenManager.java代码示例：
  ```
  package com.example.cloudphonecontroller.network;
  import com.example.cloudphonecontroller.network.ApiService;
  import com.example.cloudphonecontroller.base.ResponseBean;
  import com.example.cloudphonecontroller.constant.Constant;
  import com.example.cloudphonecontroller.model.request.AuthRequest;
  import com.example.cloudphonecontroller.model.response.AuthResponse;
  import java.io.IOException;
  import retrofit2.Response;
  public class TokenManager {
      private static String currentToken;
      // 同步获取 Token（需在子线程运行）
      public static synchronized String fetchToken() throws IOException, RuntimeException {
          AuthRequest authRequest = new AuthRequest();
          authRequest.auth = new AuthRequest.Auth();
          authRequest.auth.identity = new AuthRequest.Identity();
          authRequest.auth.identity.password = new AuthRequest.Password();
          authRequest.auth.identity.password.user = new AuthRequest.User();
          authRequest.auth.identity.password.user.domain = new AuthRequest.Domain();
          authRequest.auth.identity.password.user.domain.name = Constant.DOMAIN_NAME;
          authRequest.auth.identity.password.user.name = Constant.USER_NAME;
          authRequest.auth.identity.password.user.password = Constant.USER_PASSWORD;
          authRequest.auth.scope = new AuthRequest.Scope();
          authRequest.auth.scope.project = new AuthRequest.Project();
          authRequest.auth.scope.project.name = Constant.PROJECT_NAME;
          Response<ResponseBean<AuthResponse>> responseBeanResponse = IAMServiceManager.createService(ApiService.class).authenticate(authRequest, true).blockingFirst();
          currentToken = responseBeanResponse.headers().get("X-Subject-Token");
          return currentToken;
      }
  }
  ```
  
- IAM服务管理IAMServiceManager.java代码示例：
  ```
  import com.example.cloudphonecontroller.constant.Constant;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.concurrent.TimeUnit;
  import okhttp3.OkHttpClient;
  import okhttp3.Protocol;
  import retrofit2.Retrofit;
  import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
  import retrofit2.converter.gson.GsonConverterFactory;
  public class IAMServiceManager {
      private static Retrofit retrofit = null;
      private static String baseUrl = Constant.IAM_BASE_URL;
      public static <T> T createService(Class<T> serviceClass) {
          Retrofit retrofit = initRetrofit();
          return retrofit.create(serviceClass);
      }
      private static synchronized Retrofit initRetrofit() {
          if (retrofit != null) {
              return retrofit;
          }
          OkHttpClient.Builder builder = new OkHttpClient.Builder();
          builder.connectTimeout(15, TimeUnit.SECONDS) // 连接超时时间设置
                  .readTimeout(15, TimeUnit.SECONDS) // 读取超时时间设置
                  .protocols(Collections.unmodifiableList(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1)));
          // 初始化Retrofit
          retrofit = new Retrofit.Builder()
                  .client(builder.build())
                  .baseUrl(baseUrl)
                  .addConverterFactory(GsonConverterFactory.create())
                  .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                  .build();
          return retrofit;
      }
  }
  ```
  
- 云手机服务管理KooPhoneServiceManager.java代码示例：
  ```
  import com.example.cloudphonecontroller.constant.Constant;
  import java.security.SecureRandom;
  import java.security.cert.CertificateException;
  import java.security.cert.X509Certificate;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.concurrent.TimeUnit;
  import javax.net.ssl.HostnameVerifier;
  import javax.net.ssl.SSLContext;
  import javax.net.ssl.SSLSession;
  import javax.net.ssl.SSLSocketFactory;
  import javax.net.ssl.TrustManager;
  import javax.net.ssl.X509TrustManager;
  import okhttp3.OkHttpClient;
  import okhttp3.Protocol;
  import okhttp3.Request;
  import retrofit2.Retrofit;
  import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
  import retrofit2.converter.gson.GsonConverterFactory;
  public class KooPhoneServiceManager {
      private static Retrofit retrofit = null;
      private static String baseUrl = Constant.API_BASE_URL;
      private static String m_iamtoken = "";
      public static <T> T createService(Class<T> serviceClass,String iamtoken) {
          m_iamtoken = iamtoken;
          retrofit = initRetrofit();
          return retrofit.create(serviceClass);
      }
      public static synchronized Retrofit initRetrofit() {
          if (retrofit != null) {
              return retrofit;
          }
          OkHttpClient.Builder builder = new OkHttpClient.Builder();
          try {
   
              final TrustManager[] trustAllCerts = new TrustManager[]{
                      new X509TrustManager() {
                          @Override
                          public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                          }
                          @Override
                          public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                          }
                          @Override
                          public X509Certificate[] getAcceptedIssuers() {
                              return new X509Certificate[]{};
                          }
                      }
              };
              // 初始化SSLContext
              SSLContext sslContext = SSLContext.getInstance("SSL");
              sslContext.init(null, trustAllCerts, new SecureRandom());
              // 获取SSLSocketFactory
              SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
              // 配置OkHttp使用自定义的SSL
              builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
              // 跳过主机名验证
              builder.hostnameVerifier(new HostnameVerifier() {
                  @Override
                  public boolean verify(String hostname, SSLSession session) {
                      return true; // 信任所有主机名
                  }
              });
          } catch (Exception e) {
              e.printStackTrace();
          }
          builder.addInterceptor(chain -> {
                      Request original = chain.request();
                      Request.Builder requestBuilder = original.newBuilder();
                      if (!original.url().toString().contains("/login")) {
                          requestBuilder.header("x-auth-token", m_iamtoken);
                      }
                      return chain.proceed(requestBuilder.build());
                  })
                  .connectTimeout(15, TimeUnit.SECONDS) // 连接超时时间设置
                  .readTimeout(15, TimeUnit.SECONDS) // 读取超时时间设置
                  .protocols(Collections.unmodifiableList(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1)));
          // 初始化Retrofit
          retrofit = new Retrofit.Builder()
                  .client(builder.build())
                  .baseUrl(baseUrl)
                  .addConverterFactory(GsonConverterFactory.create())
                  .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                  .build();
          return retrofit;
      }
  }
  ```
  
- 数据订阅者工具DataSubscriber.java代码示例：
  ```
  import io.reactivex.disposables.Disposable;
  public abstract class DataSubscriber<T> extends DefaultSubscriber<T> {
      public DataSubcriber() {
          super();
      }
      @Override
      public void onSubscribe(Disposable d) {
      }
      @Override
      public void onComplete() {
      }
      @Override
      public void onError(Throwable e) {
      }
  }
  ```
  
- 数据订阅者抽象类DefaultSubscriber.java代码示例：
  ```
  package com.example.cloudphonecontroller.util;
  import io.reactivex.Observer;
  public abstract class DefaultSubscriber<T> implements Observer<T> {
  }
  ```
  
- 公共基类BaseEntity.Java代码示例：
  ```
  package com.example.cloudphonecontroller.base;
  import java.io.Serializable;
  public class BaseEntity implements Serializable {
  }
  ```
  

- 统一接口返回数据封装类ResponseBean.java代码示例
  ```
  package com.example.cloudphonecontroller.base;
  public class ResponseBean<T> extends BaseEntity {
      private boolean success;
      private String code;
      private String msg;
      private T data;
      public String getCode() {
          return code;
      }
      public void setCode(String code) {
          this.code = code;
      }
      public String getMsg() {
          return msg;
      }
      public void setMsg(String msg) {
          this.msg = msg;
      }
      public T getData() {
          return data;
      }
      public void setData(T data) {
          this.data = data;
      }
      public boolean getSuccess() {
          return success;
      }
      public void setSuccess(boolean success) {
          this.success = success;
      }
  }
  ```
  
#### 关键代码示例
```
sample/
├── app/
│   ├── src/main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       ├── cloud/keyfunction/
│   │   │       │   ├── MainActivity.java        # 主流程入口（初始化 + 串流启动）
│   │   │       │   └── Config.java              # 基础运行参数配置
│   │   │       │
│   │   │       └── KooCabin/
│   │   │           ├── api/
│   │   │           │   └── ApiService.java      # 接口定义（IAM / 业务接口）
│   │   │           │
│   │   │           ├── bean/
│   │   │           │   ├── request/
│   │   │           │   │   └── AuthRequest.java
│   │   │           │   └── response/
│   │   │           │       ├── AuthResponse.java
│   │   │           │       └── ApiResponse.java
│   │   │           │
│   │   │           ├── constant/
│   │   │           │   └── Constant.java
│   │   │           │
│   │   │           ├── iInterface/
│   │   │           │   └── CloudPhoneCallBack.java  # SDK回调
│   │   │           │
│   │   │           ├── manager/
│   │   │           │   ├── TokenManager.java
│   │   │           │   ├── IAMServiceManager.java
│   │   │           │   └── KooPhoneServiceManager.java
│   │   │           │
│   │   │           ├── subscriber/
│   │   │           │   ├── DataSubcriber.java
│   │   │           │   └── DefualtSubscriber.java
│   │   │           │
│   │   │           └── ui/base/
│   │   │               ├── BaseEntity.java
│   │   │               └── ResponseBean.java
│   │   │
│   │   ├── res/
│   │   │   ├── layout/
│   │   │   │   └── activity_main.xml
│   │   │   ├── drawable/
│   │   │   ├── mipmap*/
│   │   │   ├── values/
│   │   │   └── xml/
│   │   │
│   │   └── AndroidManifest.xml
│   │
│   ├── libs/               # SDK aar 包
│   └── build.gradle
│
├── build.gradle
├── settings.gradle
└── gradle.properties
```
**MainActivity.java**
```
package com.cloud.keyfunction;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_EVENT_EXECUTOR;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_EVENT_INTERVAL_PROCESS_MS;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_HIGH_PERF_FRAME;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_LAUNCH_KEY_VENC_TYPE;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_LAUNCH_SENSOR_INTERVAL;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_TOUCH_INPUT_METHOD;
import static com.cloudapp.client.api.CloudAppConst.CLOUD_APP_USE_PROXY_SERVER;
import static com.cloudphone.client.api.CloudPhoneConst.CLOUD_APP_AUDIO_MAX_BUFFER_SIZE;
import static com.cloudphone.client.api.CloudPhoneConst.CLOUD_APP_HIDE_STREAM_AT_START_UP;
import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_ADDRESS;
import static com.cloudphone.client.api.CloudPhoneConst.CLOUD_APP_LAUNCH_KEY_AUDIO_ENCODE_TYPE;
import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_PORT;
import static com.cloudphone.client.api.CloudPhoneConst.CLOUD_APP_LAUNCH_KEY_PROFILE_LEVEL;
import static com.cloudphone.client.api.CloudPhoneConst.LAUNCH_KEY_TOKEN;
import static com.cloudphone.client.api.CloudPhoneConst.PROFILE_SPEED;
import static com.nbc.acsdk.adapter.IPlayer.HMTP_PLAYER;
import android.app.Dialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import com.KooCabin.subscriber.DataSubcriber;
import com.nbc.utils.Log;
import android.util.Base64;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.KooCabin.api.ApiService;
import com.KooCabin.bean.response.ApiResponse;
import com.KooCabin.constant.Constant;
import com.KooCabin.iInterface.CloudPhoneCallBack;
import com.KooCabin.manager.KooPhoneServiceManager;
import com.KooCabin.manager.TokenManager;
import com.cloudphone.client.api.CloudPhoneClient;
import com.cloudphone.client.api.CloudPhoneConst;
import com.cloudphone.client.bean.AppInfo;
import com.cloudphone.client.bean.AppOperateResponse;
import com.cloudphone.client.bean.AppTypeEnum;
import com.cloudphone.client.bean.GetAppsReq;
import com.cloudphone.client.bean.GetAppsRsp;
import com.nbc.acsdk.adapter.IPlayer;
import com.nbc.acsdk.hmtp.HmtpPlayerWrapper;
import com.nbc.acsdk.media.play.AudioPlayerConfig;
import com.nbc.acsdk.widget.PlayerFragment;
import java.io.File;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private ArrayList<Button> appButtonList = new ArrayList<>();
    private Map<Button,String> appButtonMap = new HashMap();
    private Map<Button,String> sysButtonMap = new HashMap();
    Button btnBackMain;
    Button btnCloseApp;
    Button btnTaskMng;
    Button btnExit;
    View frontView;
    private String currentRunningApp = "";
    PlayerFragment m_fragment = null;
    boolean streamStarted = false;
//    private Handler audioHandler;
    private ExecutorService threadPool;
    private CompositeDisposable disposables;
    private static MainActivity instance;
    private static final int AUDIO_DELAY_MS = 100;
    //是否是合一版本，串流时bundle参数不一样
    boolean isCombinedVersion = true;
    public static MainActivity getInstance() {
        return instance;
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        instance = this;
        // 初始化资源管理器
//        audioHandler = new Handler(Looper.getMainLooper());
        threadPool = Executors.newFixedThreadPool(3);
        disposables = new CompositeDisposable();
        // 初始化SDK
        initCloudPhoneSDK();
        Log.info(TAG,"从这里开始 MainActivity onCreate");
        // 初始化UI
        initUI();
        // 开始连接云手机
        fetchTokenAndStartSteam();
    }
    private void initCloudPhoneSDK() {
        CloudPhoneClient.init(this, null, true);
        CloudPhoneClient.playerForceTextureView(false);
        CloudPhoneClient.setLogFolder(this.getExternalFilesDir("log").getPath() + File.separator);
    }
    private void initUI() {
        frontView = findViewById(R.id.black_overlay);
        findAppButtonAndAdd2Map();
        findSysButtonAndAdd2Map();
        setSysButtonClick();
        if (m_fragment == null) {
            m_fragment = new PlayerFragment();
            getFragmentManager()
                    .beginTransaction()
                    .add(R.id.containerf, m_fragment, "player_fragment")
                    .commit();
            CloudPhoneClient.bindFragment(m_fragment);
        }
        setAppButtonVisible(false);
    }
    @Override
    protected void onStart() {
        super.onStart();
        Log.info(TAG,"MainActivity onStart");
    }
    @Override
    protected void onStop() {
        super.onStop();
        Log.info(TAG,"MainActivity onStop");
    }
    @Override
    protected void onPause() {
        super.onPause();
        Log.info(TAG,"MainActivity onPause");
    }
    @Override
    protected void onResume() {
        super.onResume();
        Log.info(TAG,"MainActivity onResume");
    }
    @Override
    protected void onDestroy() {
        Log.info(TAG,"MainActivity.onDestroy");
        // 清理所有资源
        cleanupResources();
        super.onDestroy();
    }
    /**
     * 找到各app按钮并添加到Map中
     */
    void findAppButtonAndAdd2Map(){
        Button btnStartApp1 = findViewById(R.id.btnStartApp1);
        Button btnStartApp2 = findViewById(R.id.btnStartApp2);
        Button btnStartApp3 = findViewById(R.id.btnStartApp3);
        Button btnStartApp4 = findViewById(R.id.btnStartApp4);
        Button btnStartApp5 = findViewById(R.id.btnStartApp5);
        Button btnStartApp6 = findViewById(R.id.btnStartApp6);
        Button btnStartApp7 = findViewById(R.id.btnStartApp7);
        Button btnStartApp8 = findViewById(R.id.btnStartApp8);
        Button btnStartApp9 = findViewById(R.id.btnStartApp9);
        Button btnStartApp10 = findViewById(R.id.btnStartApp10);
        Button btnStartApp11 = findViewById(R.id.btnStartApp11);
        Button btnStartApp12 = findViewById(R.id.btnStartApp12);
        Button btnStartApp13 = findViewById(R.id.btnStartApp13);
        Button btnStartApp14 = findViewById(R.id.btnStartApp14);
        Button btnStartApp15 = findViewById(R.id.btnStartApp15);
        Button btnStartApp16 = findViewById(R.id.btnStartApp16);
        Button btnStartApp17 = findViewById(R.id.btnStartApp17);
        Button btnStartApp18 = findViewById(R.id.btnStartApp18);
        Button btnStartApp19 = findViewById(R.id.btnStartApp19);
        Button btnStartApp20 = findViewById(R.id.btnStartApp20);
        Button btnStartApp21 = findViewById(R.id.btnStartApp21);
        Button btnTop = findViewById(R.id.btn_top);
        btnTop.setText("顶部留空                  当前使用的实例ID: "+Constant.INSTANCE_ID);
        appButtonList.add(btnStartApp1);
        appButtonList.add(btnStartApp2);
        appButtonList.add(btnStartApp3);
        appButtonList.add(btnStartApp4);
        appButtonList.add(btnStartApp5);
        appButtonList.add(btnStartApp6);
        appButtonList.add(btnStartApp7);
        appButtonList.add(btnStartApp8);
        appButtonList.add(btnStartApp9);
        appButtonList.add(btnStartApp10);
        appButtonList.add(btnStartApp11);
        appButtonList.add(btnStartApp12);
        appButtonList.add(btnStartApp13);
        appButtonList.add(btnStartApp14);
        appButtonList.add(btnStartApp15);
        appButtonList.add(btnStartApp16);
        appButtonList.add(btnStartApp17);
        appButtonList.add(btnStartApp18);
        appButtonList.add(btnStartApp19);
        appButtonList.add(btnStartApp20);
        appButtonList.add(btnStartApp21);
    }
    /**
     * 找到返回主屏、关闭应用、重启云机、退出云机按钮 并添加到Map中
     */
    void findSysButtonAndAdd2Map(){
        btnBackMain = findViewById(R.id.btnBackMain);
        btnCloseApp = findViewById(R.id.btnCloseApp);
        btnTaskMng= findViewById(R.id.btnTaskMng);
        btnExit = findViewById(R.id.btnExit);
        sysButtonMap.put(btnBackMain,"");
        sysButtonMap.put(btnCloseApp,"");
        sysButtonMap.put(btnTaskMng,"");
        sysButtonMap.put(btnExit,"");
    }
    /**
     * 设置单击各app按钮后的触发的事件
     */
    void setAppButtonClick() {
        for (Map.Entry<Button, String> entry : appButtonMap.entrySet()) {
            final Button button = entry.getKey();
            final String pkgname = entry.getValue();
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    launchAppWithAudio(pkgname);
                }
            });
        }
    }
    /**
     * 设置launchAppWithAudio
     */
    private void launchAppWithAudio(String packageName) {
        Log.info(TAG, "调用SDK 启动应用<" + packageName + ">");
        CloudPhoneClient.startApp(packageName);
        currentRunningApp = packageName;
//        startAudio();
        CloudPhoneClient.switchStreamType(CloudPhoneConst.CLOUD_APP_SWITCH_STREAM_TYPE.BOTH);
        showFragment();
    }
    /**
     * 设置单击返回主屏、关闭应用、重启云机、退出云机按钮后的触发的事件
     */
    void setSysButtonClick(){
        btnBackMain.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.info(TAG,"返回主屏 按钮被单击了");
                CloudPhoneClient.sendKeyEvent(KeyEvent.KEYCODE_HOME);
                CloudPhoneClient.switchStreamType(CloudPhoneConst.CLOUD_APP_SWITCH_STREAM_TYPE.AUDIO);
                showAppWall();
            }
        });
        btnCloseApp.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.info(TAG,"关闭应用 按钮被单击了 "+currentRunningApp);
                CloudPhoneClient.switchStreamType(CloudPhoneConst.CLOUD_APP_SWITCH_STREAM_TYPE.DATA);
                if(!currentRunningApp.isEmpty())
                {
                    CloudPhoneClient.closeApp(currentRunningApp);
                }else{
                    Log.error(TAG,"currentRunningApp isEmpty ");
                }
                showAppWall();
            }
        });
        btnTaskMng.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.info(TAG,"触发任务管理器");
                GetAppsReq getAppsReq = new GetAppsReq();
                getAppsReq.setAppType(AppTypeEnum.THIRD_APP);
                getAppsReq.setNeedIcon(true);
                getAppsReq.setPageNum(1);
                getAppsReq.setPageSize(20);
                getAppsReq.setQuality(70);
                CloudPhoneClient.getRunningApps(getAppsReq);
            }
        });
        btnExit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.info(TAG,"退出云机 ");
                System.exit(0);
            }
        });
    }
    /**
     * //展示应用墙界面
     */
    private void showAppWall(){
        frontView.setVisibility(View.VISIBLE);
        setAppButtonVisible(true);
        findViewById(R.id.btn_top).setVisibility(View.VISIBLE);
        Log.info(TAG,"显示应用墙页面");
    }
    /**
     * //展示串流界面
     */
    private void showFragment(){
        frontView.setVisibility(View.INVISIBLE);
        setAppButtonVisible(false);
        findViewById(R.id.btn_top).setVisibility(View.INVISIBLE);
        Log.info(TAG,"显示串流页面");
    }
    void setAppButtonVisible(boolean b){
        for(int i=0;i<appButtonList.size();i++)
        {
            appButtonList.get(i).setVisibility(View.INVISIBLE);
        }
        for (Map.Entry<Button, String> entry : appButtonMap.entrySet()) {
            final Button button = entry.getKey();
            if(b){
                button.setVisibility(View.VISIBLE);
            }else{
                button.setVisibility(View.INVISIBLE);
            }
        }
    }
    private void fetchTokenAndStartSteam() {
        threadPool.execute(() -> {
            try {
                if(Constant.INSTANCE_ID.isEmpty()){
                    this.runOnUiThread(() -> Toast.makeText(getApplicationContext(), "请在gradle.properties里填入 INSTANCE_ID", Toast.LENGTH_LONG).show());
                    return;
                }
                String result= TokenManager.fetchToken(); //拿到IAMtoken
                if(result.equals(""))
                {
                    Log.error(TAG, "拿到IAMtoken 为空 ");
                }
                Observable<ApiResponse> deviceInfo = KooPhoneServiceManager.createService(ApiService.class,result).getDeviceInfo(Constant.INSTANCE_ID); //拿到DeviceToken
                executeAuthTokenAndStartStream(deviceInfo);
            } catch (Exception e) {
                Log.error(TAG, "Token initialization failed", e);
            }
        });
    }
    private void executeAuthTokenAndStartStream(Observable<ApiResponse> deviceInfo) {
        // 订阅authToken接口
        deviceInfo.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DataSubcriber<ApiResponse>() {
                    @Override
                    public void onNext(ApiResponse authResponse) {
                        if(authResponse.getData()==null||authResponse.getData().getDevice_token()==null||authResponse.getData().getResource()==null||
                                authResponse.getData().getResource().getRtc().getIce_signaling().getSignaling_url()==null)
                        {
                            MainActivity.this.runOnUiThread(() -> Toast.makeText(getApplicationContext(), "获取的Token等信息为null 请检查云机状态", Toast.LENGTH_LONG).show());
                            return;
                        }
                        Log.info(TAG,"S1------------- 成功获取token");
                        startStream(authResponse);
                    }
                    @Override
                    public void onError(Throwable e) {
                        Log.info(TAG,"onError "+e.toString());
                        MainActivity.this.runOnUiThread(() -> Toast.makeText(getApplicationContext(), "获取Token失败 请检查网络配置及云机ID是否正确  "+e.toString(), Toast.LENGTH_LONG).show());
                    }
                    @Override
                    public void onComplete() {
                    }
                });
    }
    public void startStream(ApiResponse authResponseResponseBean) {
        Bundle bundle = createBundle(authResponseResponseBean);
        CloudPhoneCallBack callBack = CloudPhoneCallBack.getInstance();
        CloudPhoneClient.setPlayerType(HMTP_PLAYER);
        CloudPhoneClient.enableAudioKeeping(true);
        CloudPhoneClient.setPlayerCallback(callBack);
        CloudPhoneClient.setAppCallback(callBack);
        CloudPhoneClient.start(null, bundle); //合一版本 使用null
        Log.info(TAG,"调用SDK 初始化数据通道串流");
    }
    /**
     * 创建重连所需的Bundle（需根据项目实际逻辑实现）
     */
    private Bundle createBundle(ApiResponse authResponse) {
        Bundle bundle = new Bundle();
        if (authResponse != null && null != authResponse.getData()) {
            ApiResponse.Data data = authResponse.getData();
            bundle.putString(LAUNCH_KEY_TOKEN, TextUtils.isEmpty(data.getDevice_token()) ? "" : data.getDevice_token());
            ApiResponse.Data.Resource resource = data.getResource();
            if (null != resource) {
                ApiResponse.Data.Resource.Sdk sdk = resource.getSdk();
                if (null != sdk) {
                    ApiResponse.Data.Resource.Sdk.External external = sdk.getExternal();
                    if (null != external) {
                        bundle.putString(LAUNCH_KEY_ADDRESS, external.getAddress());
                        bundle.putInt(LAUNCH_KEY_PORT, external.getAport());
                        
                    }
                }
            }
        }
        bundle.putInt(CLOUD_APP_LAUNCH_KEY_VENC_TYPE, 0);
        bundle.putInt(CLOUD_APP_LAUNCH_KEY_PROFILE_LEVEL, PROFILE_SPEED);
        bundle.putString("userId", Config.USER_ID);
        bundle.putBoolean(CLOUD_APP_HIDE_STREAM_AT_START_UP, true);
        bundle.putBoolean("free_aspect", true);
        bundle.putInt("screen_width", Config.SCREEN_WIDTH);//任意分辨率宽
        bundle.putInt("screen_height", Config.SCREEN_HEIGHT);//任意分辨率高
        //合一版本
        if(isCombinedVersion)
        {
            bundle.putInt(CLOUD_APP_EVENT_EXECUTOR, 1); //ignition策略
            bundle.putInt(CLOUD_APP_TOUCH_INPUT_METHOD,1);//触控方式
            bundle.putInt(CLOUD_APP_EVENT_INTERVAL_PROCESS_MS,4);//mright间隔
            bundle.putBoolean(CLOUD_APP_USE_PROXY_SERVER,false);//代理
            bundle.putBoolean(CLOUD_APP_HIGH_PERF_FRAME,false);//补帧策略
            bundle.putInt(CLOUD_APP_LAUNCH_SENSOR_INTERVAL,1000);//传感器间隔再把这坨加上
            //aac需求
            bundle.putInt(CLOUD_APP_AUDIO_MAX_BUFFER_SIZE, 19200*8);
            bundle.putInt(CLOUD_APP_LAUNCH_KEY_AUDIO_ENCODE_TYPE, 0); // OPUS - 0 / PCM - 1
        }
        return  bundle;
    }
    public void setStreamStarted(){
        if(!streamStarted){
            Log.info(TAG, "首次streamStarted 调getInstalledApps尝试获取应用列表");
            GetAppsReq getAppsReq = new GetAppsReq();
            getAppsReq.setAppType(AppTypeEnum.THIRD_APP);
            getAppsReq.setNeedIcon(false);
            getAppsReq.setPageNum(1);
            getAppsReq.setPageSize(20);
            getAppsReq.setQuality(85);
            CloudPhoneClient.getInstalledApps(getAppsReq);
        }
        streamStarted = true;
    }
    public void updateAppList(AppOperateResponse<GetAppsRsp> appOperateResponse) {
        Log.info(TAG, "处理已安装应用列表 显示按钮");
        int count = appOperateResponse.getData().getTotalCount();
        for(int i=0;i<count&&i<20;i++) //超20个会报错，先仅支持20个，后续扩展
        {
            AppInfo ai = appOperateResponse.getData().getAppList().get(i);
            Log.info(TAG, "包名及应用名  "+ai.getPackageName() + "  "+ai.getAppName());
            appButtonList.get(i).setText(ai.getAppName());
            appButtonMap.put(appButtonList.get(i),ai.getPackageName());
        }
        this.setAppButtonClick();
        showAppWall();
    }
    //触发任务管理，获取到当前正在运行的应用列表后，进行显示
    public void setRunningApps(AppOperateResponse<GetAppsRsp> appOperateResponse){
        if (!appOperateResponse.getData().getAppList().isEmpty()) {
            Log.info("onGetRunningApps", "遍历返回的list size=" +appOperateResponse.getData().getAppList().size());
            for(int i=0;i<appOperateResponse.getData().getAppList().size();i++)
            {
                Log.info("onGetRunningApps", " appName: "+appOperateResponse.getData().getAppList().get(i).getAppName()
                        +" packageName: "+appOperateResponse.getData().getAppList().get(i).getPackageName()
                        + " iconBytes: "+appOperateResponse.getData().getAppList().get(i).getIconBytes().length()
                );
            }
        }
        showImages(appOperateResponse);
    }
    private void showImages(AppOperateResponse<GetAppsRsp> appOperateResponse) {
        // 创建全屏Dialog
        Dialog dialog = new Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
        // 创建垂直布局
        LinearLayout mainLayout = new LinearLayout(this);
        mainLayout.setOrientation(LinearLayout.VERTICAL);
        mainLayout.setBackgroundColor(0xFF000000);
        // 添加关闭按钮
        Button closeBtn = new Button(this);
        closeBtn.setText("← 任务管理");
        closeBtn.setOnClickListener(v -> dialog.dismiss());
        // 设置按钮样式
        closeBtn.setTextColor(0xFFFFFFFF);
        closeBtn.setBackgroundColor(0xFFFF0000);
        LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
        );
        btnParams.setMargins(0, 0, 0, 20);
        closeBtn.setLayoutParams(btnParams);
        mainLayout.addView(closeBtn);
        // 创建两行水平布局
        LinearLayout row1 = new LinearLayout(this);
        row1.setOrientation(LinearLayout.HORIZONTAL);
        LinearLayout row2 = new LinearLayout(this);
        row2.setOrientation(LinearLayout.HORIZONTAL);
        // 设置行布局参数
        LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                0
        );
        rowParams.weight = 1;
        rowParams.setMargins(0, 0, 0, 10);
        row1.setLayoutParams(rowParams);
        row2.setLayoutParams(rowParams);
        // 获取屏幕宽度
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        int screenWidth = displayMetrics.widthPixels;
        // 计算图片宽度（一行3个，减去间距后除以3）
        int imageWidth = (screenWidth - 4 * 20) / 3; // 4个间距，每个20dp
        // 根据16:10的比例计算高度
        int imageHeight = (int) (imageWidth * 10.0f / 16.0f);
// 应用名数组
        String[] appNames = new String[appOperateResponse.getData().getAppList().size()];
        for(int i=0;i<appNames.length;i++)
        {
            appNames[i] = appOperateResponse.getData().getAppList().get(i).getAppName();
        }
        // 引用图标Base64数组（假设是小的引用图标）
//        String[] iconBase64Images = {"图标1base64", "图标2base64", "图标3base64",
//                "图标4base64", "图标5base64", "图标6base64"};
        String[] iconBase64Images = new String[appOperateResponse.getData().getAppList().size()];
        for(int i=0;i<iconBase64Images.length;i++)
        {
            iconBase64Images[i] = appOperateResponse.getData().getAppList().get(i).getIconBytes();
        }
        // 添加你的图片（最多6张）
//        String[] base64Images = {"图片1base64", "图片2base64", "图片3base64",
//                "图片4base64", "图片5base64", "图片6base64"};
        String[] base64Images = new String[appOperateResponse.getData().getAppList().size()];
        for(int i=0;i<base64Images.length;i++)
        {
            base64Images[i] = appOperateResponse.getData().getAppList().get(i).getScreenshot();
        }
        for (int i = 0; i < Math.min(base64Images.length, 6); i++) {
            try {
                // 为每个应用创建垂直布局
                LinearLayout appItem = new LinearLayout(this);
                appItem.setOrientation(LinearLayout.VERTICAL);
                // 设置应用项布局参数
                LinearLayout.LayoutParams appItemParams = new LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.WRAP_CONTENT,
                        LinearLayout.LayoutParams.WRAP_CONTENT
                );
                appItemParams.setMargins(10, 0, 10, 0);
                appItem.setLayoutParams(appItemParams);
                // 1. 创建顶部水平布局（引用图标 + 应用名 + 关闭按钮）
                LinearLayout topBar = new LinearLayout(this);
                topBar.setOrientation(LinearLayout.HORIZONTAL);
                topBar.setLayoutParams(new LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.MATCH_PARENT,
                        LinearLayout.LayoutParams.WRAP_CONTENT
                ));
                topBar.setPadding(5, 5, 5, 5);
                // 1.1 引用图标
                ImageView iconView = new ImageView(this);
                String iconBase64 = iconBase64Images[i];
                if (iconBase64.contains(",")) {
                    iconBase64 = iconBase64.split(",")[1];
                }
                byte[] iconBytes = Base64.decode(iconBase64, Base64.DEFAULT);
                Bitmap iconBitmap = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.length);
                iconView.setImageBitmap(iconBitmap);
                // 设置图标大小
                int iconSize = 30; // 30dp
                LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
                        dpToPx(iconSize), dpToPx(iconSize)
                );
                iconParams.setMargins(0, 0, dpToPx(5), 0);
                iconView.setLayoutParams(iconParams);
                topBar.addView(iconView);
                // 1.2 应用名
                TextView appNameView = new TextView(this);
                appNameView.setText(appNames[i]);
                appNameView.setTextColor(0xFFFFFFFF);
                appNameView.setTextSize(12);
                appNameView.setGravity(Gravity.CENTER_VERTICAL);
                LinearLayout.LayoutParams nameParams = new LinearLayout.LayoutParams(
                        0, LinearLayout.LayoutParams.WRAP_CONTENT
                );
                nameParams.weight = 1; // 占据剩余空间
                nameParams.setMargins(0, 0, dpToPx(5), 0);
                appNameView.setLayoutParams(nameParams);
                topBar.addView(appNameView);
                // 1.3 关闭按钮 - 修改高度
                Button closeAppBtn = new Button(this);
                closeAppBtn.setText("结束应用");
                closeAppBtn.setTextSize(10);
                closeAppBtn.setTextColor(0xFFFFFFFF);
                closeAppBtn.setBackgroundColor(0xFFFF4444);
                closeAppBtn.setPadding(dpToPx(5), dpToPx(2), dpToPx(5), dpToPx(2));
                // 设置关闭按钮的布局参数，高度设置为原来一半
                LinearLayout.LayoutParams closeBtnParams = new LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.WRAP_CONTENT,
                        dpToPx(20)  // 高度改为原来的一半（图标是30dp，一半是15dp）
                );
                closeBtnParams.gravity = Gravity.CENTER_VERTICAL;  // 垂直居中
                closeAppBtn.setLayoutParams(closeBtnParams);
                // 为关闭按钮设置单击事件
                int finalI = i;
                closeAppBtn.setOnClickListener(v -> {
                    // 结束应用的逻辑
                    Toast.makeText(this, "结束应用: " + appNames[finalI], Toast.LENGTH_SHORT).show();
                    // 这里可以添加实际结束应用的代码
                });
                topBar.addView(closeAppBtn);
                appItem.addView(topBar);
                // 2. 创建主图片
                String cleanBase64 = base64Images[i];
                if (cleanBase64.contains(",")) {
                    cleanBase64 = cleanBase64.split(",")[1];
                }
                byte[] bytes = Base64.decode(cleanBase64, Base64.DEFAULT);
                Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                ImageView iv = new ImageView(this);
                iv.setImageBitmap(bitmap);
                iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
                // 为主图片添加单击事件
                int finalI1 = i;
                iv.setOnClickListener(v -> {
                    // 处理主图片单击事件
                    Toast.makeText(this, "单击了: " + appNames[finalI1], Toast.LENGTH_SHORT).show();
                    // 这里可以添加单击后的操作，比如跳转到应用详情等
                });
                // 设置图片布局参数
                LinearLayout.LayoutParams imgParams = new LinearLayout.LayoutParams(
                        imageWidth, imageHeight
                );
                imgParams.setMargins(0, dpToPx(5), 0, 0);
                imgParams.gravity = Gravity.CENTER_HORIZONTAL;
                iv.setLayoutParams(imgParams);
                appItem.addView(iv);
                // 添加到对应的行
                if (i < 3) {
                    row1.addView(appItem);
                } else {
                    row2.addView(appItem);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 添加行布局到主布局
        mainLayout.addView(row1);
        mainLayout.addView(row2);
        dialog.setContentView(mainLayout);
        dialog.show();
    }
    // dp转px的工具方法
    private int dpToPx(int dp) {
        return (int) (dp * getResources().getDisplayMetrics().density);
    }
    private void cleanupResources() {
        // 停止云手机
        if (streamStarted) {
            try {
                CloudPhoneClient.stop();
            } catch (Exception e) {
                Log.error(TAG, "停止云手机失败",e);
            }
        }
        // 解绑Fragment
        if (m_fragment != null) {
            try {
                getFragmentManager().beginTransaction()
                        .remove(m_fragment)
                        .commitAllowingStateLoss();
                m_fragment = null;
                Log.info(TAG,"Fragment已移除");
            } catch (Exception e) {
                Log.error(TAG, "解绑Fragment失败",e);
            }
        }
        // 关闭线程池
        if (threadPool != null) {
            threadPool.shutdown();
            try {
                if (!threadPool.awaitTermination(500, TimeUnit.MILLISECONDS)) {
                    threadPool.shutdownNow();
                }
            } catch (InterruptedException e) {
                threadPool.shutdownNow();
            }
        }
        // 清理RxJava
        if (disposables != null && !disposables.isDisposed()) {
            disposables.clear();
            disposables.dispose();
        }
        // 清理集合
        appButtonList.clear();
        appButtonMap.clear();
        sysButtonMap.clear();
    }
}
```
