1 /* 2 * Copyright (C) 2022 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 17 package com.android.sdksandboxclient_shareduid; 18 19 import static android.app.sdksandbox.SdkSandboxManager.EXTRA_DISPLAY_ID; 20 import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS; 21 import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HOST_TOKEN; 22 import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SURFACE_PACKAGE; 23 import static android.app.sdksandbox.SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS; 24 25 import android.annotation.NonNull; 26 import android.app.Activity; 27 import android.app.AlertDialog; 28 import android.app.sdksandbox.LoadSdkException; 29 import android.app.sdksandbox.RequestSurfacePackageException; 30 import android.app.sdksandbox.SandboxedSdk; 31 import android.app.sdksandbox.SdkSandboxManager; 32 import android.app.sdksandbox.interfaces.ISdkApi; 33 import android.content.SharedPreferences; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.Looper; 38 import android.os.OutcomeReceiver; 39 import android.os.RemoteException; 40 import android.preference.PreferenceManager; 41 import android.text.InputType; 42 import android.util.Log; 43 import android.view.SurfaceControlViewHost.SurfacePackage; 44 import android.view.SurfaceView; 45 import android.view.View; 46 import android.widget.Button; 47 import android.widget.EditText; 48 import android.widget.LinearLayout; 49 import android.widget.Toast; 50 51 import java.util.Set; 52 53 public class MainActivity extends Activity { 54 private static final String SDK_NAME = "com.android.sdksandboxcode"; 55 private static final String TAG = "SdkSandboxClient_SharedUidMainActivity"; 56 57 private static final String VIEW_TYPE_KEY = "view-type"; 58 private static final String VIDEO_VIEW_VALUE = "video-view"; 59 private static final String VIDEO_URL_KEY = "video-url"; 60 61 private static final Handler sHandler = new Handler(Looper.getMainLooper()); 62 63 private static String sVideoUrl; 64 65 private boolean mSdkLoaded = false; 66 private SdkSandboxManager mSdkSandboxManager; 67 68 private Button mLoadButton; 69 private Button mRenderButton; 70 private Button mCreateFileButton; 71 private Button mPlayVideoButton; 72 private Button mSyncKeysButton; 73 private Button mGetKeysButton; 74 75 private SurfaceView mRenderedView; 76 77 private SandboxedSdk mSandboxedSdk; 78 79 @Override onCreate(Bundle savedInstanceState)80 public void onCreate(Bundle savedInstanceState) { 81 super.onCreate(savedInstanceState); 82 setContentView(R.layout.activity_main); 83 mSdkSandboxManager = getApplicationContext().getSystemService(SdkSandboxManager.class); 84 Bundle extras = getIntent().getExtras(); 85 if (extras != null) { 86 sVideoUrl = extras.getString(VIDEO_URL_KEY); 87 } 88 89 mRenderedView = findViewById(R.id.rendered_view); 90 mRenderedView.setZOrderOnTop(true); 91 mRenderedView.setVisibility(View.INVISIBLE); 92 93 mLoadButton = findViewById(R.id.load_sdk_button); 94 mRenderButton = findViewById(R.id.request_surface_button); 95 mCreateFileButton = findViewById(R.id.create_file_button); 96 mPlayVideoButton = findViewById(R.id.play_video_button); 97 mSyncKeysButton = findViewById(R.id.sync_keys_button); 98 mGetKeysButton = findViewById(R.id.get_keys_button); 99 100 registerLoadSdkProviderButton(); 101 registerLoadSurfacePackageButton(); 102 registerCreateFileButton(); 103 registerPlayVideoButton(); 104 registerSyncKeysButton(); 105 registerGetKeysButton(); 106 } 107 registerLoadSdkProviderButton()108 private void registerLoadSdkProviderButton() { 109 mLoadButton.setOnClickListener( 110 v -> { 111 if (!mSdkLoaded) { 112 // Register for sandbox death event. 113 mSdkSandboxManager.addSdkSandboxProcessDeathCallback( 114 Runnable::run, () -> makeToast("Sdk Sandbox process died")); 115 116 Bundle params = new Bundle(); 117 OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver = 118 new OutcomeReceiver<SandboxedSdk, LoadSdkException>() { 119 @Override 120 public void onResult(SandboxedSdk sandboxedSdk) { 121 mSdkLoaded = true; 122 123 mSandboxedSdk = sandboxedSdk; 124 125 makeToast("Loaded successfully!"); 126 mLoadButton.setText("Unload SDK"); 127 } 128 129 @Override 130 public void onError(LoadSdkException error) { 131 makeToast("Failed: " + error); 132 } 133 }; 134 mSdkSandboxManager.loadSdk(SDK_NAME, params, Runnable::run, receiver); 135 } else { 136 mSdkSandboxManager.unloadSdk(SDK_NAME); 137 mLoadButton.setText("Load SDK"); 138 mSdkLoaded = false; 139 } 140 }); 141 } 142 registerLoadSurfacePackageButton()143 private void registerLoadSurfacePackageButton() { 144 OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver = 145 new RequestSurfacePackageReceiver(); 146 mRenderButton.setOnClickListener( 147 v -> { 148 if (mSdkLoaded) { 149 sHandler.post( 150 () -> { 151 mSdkSandboxManager.requestSurfacePackage( 152 SDK_NAME, 153 getRequestSurfacePackageParams(), 154 Runnable::run, 155 receiver); 156 }); 157 } else { 158 makeToast("Sdk is not loaded"); 159 } 160 }); 161 } 162 registerCreateFileButton()163 private void registerCreateFileButton() { 164 mCreateFileButton.setOnClickListener( 165 v -> { 166 if (!mSdkLoaded) { 167 makeToast("Sdk is not loaded"); 168 return; 169 } 170 AlertDialog.Builder builder = new AlertDialog.Builder(this); 171 builder.setTitle("Set size in MB"); 172 final EditText input = new EditText(this); 173 input.setInputType(InputType.TYPE_CLASS_NUMBER); 174 builder.setView(input); 175 builder.setPositiveButton( 176 "Create", 177 (dialog, which) -> { 178 int sizeInMb = -1; 179 try { 180 sizeInMb = Integer.parseInt(input.getText().toString()); 181 } catch (Exception ignore) { 182 } 183 if (sizeInMb <= 0) { 184 makeToast("Please provide positive integer value"); 185 return; 186 } 187 IBinder binder = mSandboxedSdk.getInterface(); 188 ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder); 189 try { 190 String response = sdkApi.createFile(sizeInMb); 191 makeToast(response); 192 } catch (RemoteException e) { 193 throw new RuntimeException(e); 194 } 195 }); 196 builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel()); 197 builder.show(); 198 }); 199 } 200 registerPlayVideoButton()201 private void registerPlayVideoButton() { 202 if (sVideoUrl == null) { 203 mPlayVideoButton.setVisibility(View.GONE); 204 return; 205 } 206 207 OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver = 208 new RequestSurfacePackageReceiver(); 209 mPlayVideoButton.setOnClickListener( 210 v -> { 211 if (mSdkLoaded) { 212 sHandler.post( 213 () -> { 214 Bundle params = getRequestSurfacePackageParams(); 215 params.putString(VIEW_TYPE_KEY, VIDEO_VIEW_VALUE); 216 params.putString(VIDEO_URL_KEY, sVideoUrl); 217 mSdkSandboxManager.requestSurfacePackage( 218 SDK_NAME, params, Runnable::run, receiver); 219 }); 220 } else { 221 makeToast("Sdk is not loaded"); 222 } 223 }); 224 } 225 registerSyncKeysButton()226 private void registerSyncKeysButton() { 227 mSyncKeysButton.setOnClickListener( 228 v -> { 229 if (!mSdkLoaded) { 230 makeToast("Sdk is not loaded"); 231 return; 232 } 233 234 final AlertDialog.Builder alert = new AlertDialog.Builder(this); 235 236 alert.setTitle("Set the key and value to sync"); 237 LinearLayout linearLayout = new LinearLayout(this); 238 linearLayout.setOrientation(1); // 1 is for vertical orientation 239 final EditText inputKey = new EditText(this); 240 final EditText inputValue = new EditText(this); 241 linearLayout.addView(inputKey); 242 linearLayout.addView(inputValue); 243 alert.setView(linearLayout); 244 245 alert.setPositiveButton( 246 "Sync", 247 (dialog, which) -> { 248 sHandler.post( 249 () -> { 250 final SharedPreferences pref = 251 PreferenceManager.getDefaultSharedPreferences( 252 getApplicationContext()); 253 String keyToSync = inputKey.getText().toString(); 254 String valueToSync = inputValue.getText().toString(); 255 pref.edit().putString(keyToSync, valueToSync).commit(); 256 mSdkSandboxManager.addSyncedSharedPreferencesKeys( 257 Set.of(keyToSync)); 258 IBinder binder = mSandboxedSdk.getInterface(); 259 ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder); 260 try { 261 // Allow some time for data to sync 262 Thread.sleep(1000); 263 String syncedKeysValue = 264 sdkApi.getSyncedSharedPreferencesString( 265 keyToSync); 266 if (syncedKeysValue.equals(valueToSync)) { 267 makeToast( 268 "Key was synced successfully\n" 269 + "Key is : " 270 + keyToSync 271 + " Value is : " 272 + syncedKeysValue); 273 } else { 274 makeToast("Key was not synced"); 275 } 276 } catch (Exception e) { 277 throw new RuntimeException(e); 278 } 279 }); 280 }); 281 alert.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel()); 282 alert.show(); 283 }); 284 } 285 registerGetKeysButton()286 private void registerGetKeysButton() { 287 mGetKeysButton.setOnClickListener( 288 v -> { 289 sHandler.post( 290 () -> { 291 try { 292 // Allow some time for data to sync 293 Thread.sleep(1000); 294 Set<String> syncedKeys = 295 mSdkSandboxManager.getSyncedSharedPreferencesKeys(); 296 makeToast( 297 "The number of synced keys is : " 298 + String.valueOf(syncedKeys.size()) 299 + " and the keys are : " 300 + String.join(", ", syncedKeys)); 301 } catch (Exception e) { 302 throw new RuntimeException(e); 303 } 304 }); 305 }); 306 } 307 getRequestSurfacePackageParams()308 private Bundle getRequestSurfacePackageParams() { 309 Bundle params = new Bundle(); 310 params.putInt(EXTRA_WIDTH_IN_PIXELS, mRenderedView.getWidth()); 311 params.putInt(EXTRA_HEIGHT_IN_PIXELS, mRenderedView.getHeight()); 312 params.putInt(EXTRA_DISPLAY_ID, getDisplay().getDisplayId()); 313 params.putBinder(EXTRA_HOST_TOKEN, mRenderedView.getHostToken()); 314 return params; 315 } 316 makeToast(String message)317 private void makeToast(String message) { 318 runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show()); 319 } 320 321 private class RequestSurfacePackageReceiver 322 implements OutcomeReceiver<Bundle, RequestSurfacePackageException> { 323 324 @Override onResult(Bundle result)325 public void onResult(Bundle result) { 326 sHandler.post( 327 () -> { 328 SurfacePackage surfacePackage = 329 result.getParcelable(EXTRA_SURFACE_PACKAGE, SurfacePackage.class); 330 mRenderedView.setChildSurfacePackage(surfacePackage); 331 mRenderedView.setVisibility(View.VISIBLE); 332 }); 333 makeToast("Rendered surface view"); 334 } 335 336 @Override onError(@onNull RequestSurfacePackageException error)337 public void onError(@NonNull RequestSurfacePackageException error) { 338 makeToast("Failed: " + error.getMessage()); 339 Log.e(TAG, error.getMessage(), error); 340 } 341 } 342 } 343