• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
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 import static android.util.Log.DEBUG;
25 import static android.util.Log.ERROR;
26 import static android.util.Log.INFO;
27 import static android.util.Log.VERBOSE;
28 import static android.util.Log.WARN;
29 
30 import android.annotation.NonNull;
31 import android.app.Activity;
32 import android.app.AlertDialog;
33 import android.app.sdksandbox.LoadSdkException;
34 import android.app.sdksandbox.RequestSurfacePackageException;
35 import android.app.sdksandbox.SandboxedSdk;
36 import android.app.sdksandbox.SdkSandboxManager;
37 import android.app.sdksandbox.interfaces.IActivityStarter;
38 import android.app.sdksandbox.interfaces.ISdkApi;
39 import android.content.SharedPreferences;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.Looper;
44 import android.os.OutcomeReceiver;
45 import android.os.RemoteException;
46 import android.os.StrictMode;
47 import android.preference.PreferenceManager;
48 import android.text.InputType;
49 import android.util.Log;
50 import android.view.SurfaceControlViewHost.SurfacePackage;
51 import android.view.SurfaceView;
52 import android.view.View;
53 import android.widget.Button;
54 import android.widget.EditText;
55 import android.widget.LinearLayout;
56 import android.widget.Toast;
57 
58 import com.android.modules.utils.BackgroundThread;
59 import com.android.modules.utils.build.SdkLevel;
60 
61 import java.util.Set;
62 
63 public class MainActivity extends Activity {
64     // TODO(b/253202014): Add toggle button
65     private static final Boolean IS_WEBVIEW_TESTING_ENABLED = false;
66     private static final String SDK_NAME =
67             IS_WEBVIEW_TESTING_ENABLED
68                     ? "com.android.sdksandboxcode_webview"
69                     : "com.android.sdksandboxcode";
70     private static final String MEDIATEE_SDK_NAME = "com.android.sdksandboxcode_mediatee";
71     private static final String TAG = "SdkSandboxClientMainActivity";
72 
73     private static final String VIEW_TYPE_KEY = "view-type";
74     private static final String VIDEO_VIEW_VALUE = "video-view";
75     private static final String VIDEO_URL_KEY = "video-url";
76 
77     private static final Handler sHandler = new Handler(Looper.getMainLooper());
78     private static final String EXTRA_SDK_SDK_ENABLED_KEY = "sdkSdkCommEnabled";
79 
80     private static String sVideoUrl;
81 
82     private boolean mSdksLoaded = false;
83     private boolean mSdkSdkCommEnabled = false;
84     private SdkSandboxManager mSdkSandboxManager;
85 
86     private Button mLoadButton;
87     private Button mRenderButton;
88     private Button mCreateFileButton;
89     private Button mPlayVideoButton;
90     private Button mSyncKeysButton;
91     private Button mSdkSdkCommButton;
92     private Button mStartActivity;
93 
94     private SurfaceView mRenderedView;
95 
96     private SandboxedSdk mSandboxedSdk;
97 
98     @Override
onCreate(Bundle savedInstanceState)99     public void onCreate(Bundle savedInstanceState) {
100         enableStrictMode();
101         super.onCreate(savedInstanceState);
102         setContentView(R.layout.activity_main);
103         mSdkSandboxManager = getApplicationContext().getSystemService(SdkSandboxManager.class);
104         Bundle extras = getIntent().getExtras();
105         if (extras != null) {
106             sVideoUrl = extras.getString(VIDEO_URL_KEY);
107         }
108 
109         mRenderedView = findViewById(R.id.rendered_view);
110         mRenderedView.setZOrderOnTop(true);
111         mRenderedView.setVisibility(View.INVISIBLE);
112 
113         mLoadButton = findViewById(R.id.load_code_button);
114         mRenderButton = findViewById(R.id.request_surface_button);
115         mCreateFileButton = findViewById(R.id.create_file_button);
116         mPlayVideoButton = findViewById(R.id.play_video_button);
117         mSyncKeysButton = findViewById(R.id.sync_keys_button);
118         mSdkSdkCommButton = findViewById(R.id.enable_sdk_sdk_button);
119         mStartActivity = findViewById(R.id.start_activity);
120 
121         registerLoadSdkProviderButton();
122         registerLoadSurfacePackageButton();
123         registerCreateFileButton();
124         registerPlayVideoButton();
125         registerSyncKeysButton();
126         registerSdkSdkButton();
127         registerStartActivityButton();
128     }
129 
registerLoadSdkProviderButton()130     private void registerLoadSdkProviderButton() {
131         mLoadButton.setOnClickListener(
132                 v -> {
133                     if (mSdksLoaded) {
134                         resetStateForLoadSdkButton();
135                         return;
136                     }
137                     // Register for sandbox death event.
138                     mSdkSandboxManager.addSdkSandboxProcessDeathCallback(
139                             Runnable::run, () -> toastAndLog(ERROR, "Sdk Sandbox process died"));
140 
141                     Bundle params = new Bundle();
142                     OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver =
143                             new OutcomeReceiver<SandboxedSdk, LoadSdkException>() {
144                                 @Override
145                                 public void onResult(SandboxedSdk sandboxedSdk) {
146                                     mSdksLoaded = true;
147                                     mSandboxedSdk = sandboxedSdk;
148                                     toastAndLog(INFO, "First SDK Loaded successfully!");
149                                 }
150 
151                                 @Override
152                                 public void onError(LoadSdkException error) {
153                                     toastAndLog(ERROR, "Failed to load first SDK: %s", error);
154                                 }
155                             };
156                     OutcomeReceiver<SandboxedSdk, LoadSdkException> mediateeReceiver =
157                             new OutcomeReceiver<SandboxedSdk, LoadSdkException>() {
158                                 @Override
159                                 public void onResult(SandboxedSdk sandboxedSdk) {
160                                     toastAndLog(INFO, "All SDKs Loaded successfully!");
161                                     mLoadButton.setText("Unload SDKs");
162                                 }
163 
164                                 @Override
165                                 public void onError(LoadSdkException error) {
166                                     toastAndLog(ERROR, "Failed to load all SDKs: %s", error);
167                                     resetStateForLoadSdkButton();
168                                 }
169                             };
170                     Log.i(TAG, "Loading SDKs " + SDK_NAME + " and " + MEDIATEE_SDK_NAME);
171                     mSdkSandboxManager.loadSdk(SDK_NAME, params, Runnable::run, receiver);
172                     mSdkSandboxManager.loadSdk(
173                             MEDIATEE_SDK_NAME, params, Runnable::run, mediateeReceiver);
174                 });
175     }
176 
resetStateForLoadSdkButton()177     private void resetStateForLoadSdkButton() {
178         Log.i(TAG, "Unloading SDKs " + SDK_NAME + " and " + MEDIATEE_SDK_NAME);
179         mSdkSandboxManager.unloadSdk(SDK_NAME);
180         mSdkSandboxManager.unloadSdk(MEDIATEE_SDK_NAME);
181         mLoadButton.setText("Load SDKs");
182         mSdksLoaded = false;
183     }
184 
registerLoadSurfacePackageButton()185     private void registerLoadSurfacePackageButton() {
186         OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver =
187                 new RequestSurfacePackageReceiver();
188         mRenderButton.setOnClickListener(
189                 v -> {
190                     if (mSdksLoaded) {
191                         sHandler.post(
192                                 () -> {
193                                     mSdkSandboxManager.requestSurfacePackage(
194                                             SDK_NAME,
195                                             getRequestSurfacePackageParams(),
196                                             Runnable::run,
197                                             receiver);
198                                 });
199                     } else {
200                         toastAndLog(WARN, "Sdk is not loaded");
201                     }
202                 });
203     }
204 
registerCreateFileButton()205     private void registerCreateFileButton() {
206         mCreateFileButton.setOnClickListener(
207                 v -> {
208                     if (!mSdksLoaded) {
209                         toastAndLog(WARN, "Sdk is not loaded");
210                         return;
211                     }
212                     AlertDialog.Builder builder = new AlertDialog.Builder(this);
213                     builder.setTitle("Set size in MB (1-100)");
214                     final EditText input = new EditText(this);
215                     input.setInputType(InputType.TYPE_CLASS_NUMBER);
216                     builder.setView(input);
217                     builder.setPositiveButton(
218                             "Create",
219                             (dialog, which) -> {
220                                 final int sizeInMb = Integer.parseInt(input.getText().toString());
221                                 if (sizeInMb <= 0 || sizeInMb > 100) {
222                                     toastAndLog(WARN, "Please provide a value between 1 and 100");
223                                     return;
224                                 }
225                                 IBinder binder = mSandboxedSdk.getInterface();
226                                 ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
227 
228                                 BackgroundThread.getExecutor()
229                                         .execute(
230                                                 () -> {
231                                                     try {
232                                                         String response =
233                                                                 sdkApi.createFile(sizeInMb);
234                                                         toastAndLog(INFO, response);
235                                                     } catch (Exception e) {
236                                                         toastAndLog(
237                                                                 e,
238                                                                 "Failed to create file with %d Mb",
239                                                                 sizeInMb);
240                                                     }
241                                                 });
242                             });
243                     builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
244                     builder.show();
245                 });
246     }
247 
registerPlayVideoButton()248     private void registerPlayVideoButton() {
249         if (sVideoUrl == null) {
250             mPlayVideoButton.setVisibility(View.GONE);
251             return;
252         }
253 
254         OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver =
255                 new RequestSurfacePackageReceiver();
256         mPlayVideoButton.setOnClickListener(
257                 v -> {
258                     if (mSdksLoaded) {
259                         sHandler.post(
260                                 () -> {
261                                     Bundle params = getRequestSurfacePackageParams();
262                                     params.putString(VIEW_TYPE_KEY, VIDEO_VIEW_VALUE);
263                                     params.putString(VIDEO_URL_KEY, sVideoUrl);
264                                     mSdkSandboxManager.requestSurfacePackage(
265                                             SDK_NAME, params, Runnable::run, receiver);
266                                 });
267                     } else {
268                         toastAndLog(WARN, "Sdk is not loaded");
269                     }
270                 });
271     }
272 
registerSdkSdkButton()273     private void registerSdkSdkButton() {
274         mSdkSdkCommButton.setOnClickListener(
275                 v -> {
276                     mSdkSdkCommEnabled = !mSdkSdkCommEnabled;
277                     if (mSdkSdkCommEnabled) {
278                         mSdkSdkCommButton.setText("Disable SDK SDK comm");
279                         toastAndLog(INFO, "Sdk Sdk Comm Enabled");
280                     } else {
281                         mSdkSdkCommButton.setText("Enable SDK SDK comm");
282                         toastAndLog(INFO, "Sdk Sdk Comm Disabled");
283                     }
284                 });
285     }
286 
registerSyncKeysButton()287     private void registerSyncKeysButton() {
288         mSyncKeysButton.setOnClickListener(
289                 v -> {
290                     if (!mSdksLoaded) {
291                         toastAndLog(WARN, "Sdk is not loaded");
292                         return;
293                     }
294 
295                     final AlertDialog.Builder alert = new AlertDialog.Builder(this);
296 
297                     alert.setTitle("Set the key and value to sync");
298                     LinearLayout linearLayout = new LinearLayout(this);
299                     linearLayout.setOrientation(1); // 1 is for vertical orientation
300                     final EditText inputKey = new EditText(this);
301                     inputKey.setText("key");
302                     final EditText inputValue = new EditText(this);
303                     inputValue.setText("value");
304                     linearLayout.addView(inputKey);
305                     linearLayout.addView(inputValue);
306                     alert.setView(linearLayout);
307 
308                     alert.setPositiveButton(
309                             "Sync",
310                             (dialog, which) -> {
311                                 onSyncKeyPressed(inputKey, inputValue);
312                             });
313                     alert.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
314                     alert.show();
315                 });
316     }
317 
onSyncKeyPressed(EditText inputKey, EditText inputValue)318     private void onSyncKeyPressed(EditText inputKey, EditText inputValue) {
319         BackgroundThread.getHandler()
320                 .post(
321                         () -> {
322                             final SharedPreferences pref =
323                                     PreferenceManager.getDefaultSharedPreferences(
324                                             getApplicationContext());
325                             String keyToSync = inputKey.getText().toString();
326                             String valueToSync = inputValue.getText().toString();
327                             pref.edit().putString(keyToSync, valueToSync).commit();
328                             mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of(keyToSync));
329                             IBinder binder = mSandboxedSdk.getInterface();
330                             ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
331                             try {
332                                 // Allow some time for data to sync
333                                 Thread.sleep(1000);
334                                 String syncedKeysValue =
335                                         sdkApi.getSyncedSharedPreferencesString(keyToSync);
336                                 if (syncedKeysValue.equals(valueToSync)) {
337                                     toastAndLog(
338                                             INFO,
339                                             "Key was synced successfully\n"
340                                                     + "Key is : %s Value is : %s",
341                                             keyToSync,
342                                             syncedKeysValue);
343                                 } else {
344                                     toastAndLog(WARN, "Key was not synced");
345                                 }
346                             } catch (Exception e) {
347                                 toastAndLog(e, "Failed to sync keys (%s)", keyToSync);
348                             }
349                         });
350     }
351 
registerStartActivityButton()352     private void registerStartActivityButton() {
353         mStartActivity.setOnClickListener(
354                 v -> {
355                     if (!mSdksLoaded) {
356                         toastAndLog(WARN, "Sdk is not loaded");
357                         return;
358                     }
359                     if (!SdkLevel.isAtLeastU()) {
360                         toastAndLog(WARN, "Device should have Android U or above!");
361                         return;
362                     }
363                     IBinder binder = mSandboxedSdk.getInterface();
364                     ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
365                     ActivityStarter starter = new ActivityStarter(this, mSdkSandboxManager);
366                     try {
367                         sdkApi.startActivity(starter);
368                         toastAndLog(INFO, "Started activity %s", starter);
369 
370                     } catch (RemoteException e) {
371                         toastAndLog(e, "Failed to startActivity (%s)", starter);
372                     }
373                 });
374     }
375 
getRequestSurfacePackageParams()376     private Bundle getRequestSurfacePackageParams() {
377         Bundle params = new Bundle();
378         params.putInt(EXTRA_WIDTH_IN_PIXELS, mRenderedView.getWidth());
379         params.putInt(EXTRA_HEIGHT_IN_PIXELS, mRenderedView.getHeight());
380         params.putInt(EXTRA_DISPLAY_ID, getDisplay().getDisplayId());
381         params.putBinder(EXTRA_HOST_TOKEN, mRenderedView.getHostToken());
382         params.putBoolean(EXTRA_SDK_SDK_ENABLED_KEY, mSdkSdkCommEnabled);
383         return params;
384     }
385 
toastAndLog(int logLevel, String fmt, Object... args)386     private void toastAndLog(int logLevel, String fmt, Object... args) {
387         String message = String.format(fmt, args);
388         switch (logLevel) {
389             case DEBUG:
390                 Log.d(TAG, message);
391                 break;
392             case ERROR:
393                 Log.e(TAG, message);
394                 break;
395             case INFO:
396                 Log.i(TAG, message);
397                 break;
398             case VERBOSE:
399                 Log.v(TAG, message);
400                 break;
401             case WARN:
402                 Log.w(TAG, message);
403                 break;
404             default:
405                 Log.w(TAG, "Invalid log level " + logLevel + " for message: " + message);
406         }
407         makeToast(message);
408     }
409 
toastAndLog(Exception e, String fmt, Object... args)410     private void toastAndLog(Exception e, String fmt, Object... args) {
411         String message = String.format(fmt, args);
412         Log.e(TAG, message, e);
413         makeToast(message);
414     }
415 
makeToast(CharSequence message)416     private void makeToast(CharSequence message) {
417         runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show());
418     }
419 
420     private class RequestSurfacePackageReceiver
421             implements OutcomeReceiver<Bundle, RequestSurfacePackageException> {
422 
423         @Override
onResult(Bundle result)424         public void onResult(Bundle result) {
425             sHandler.post(
426                     () -> {
427                         SurfacePackage surfacePackage =
428                                 result.getParcelable(EXTRA_SURFACE_PACKAGE, SurfacePackage.class);
429                         mRenderedView.setChildSurfacePackage(surfacePackage);
430                         mRenderedView.setVisibility(View.VISIBLE);
431                     });
432             toastAndLog(INFO, "Rendered surface view");
433         }
434 
435         @Override
onError(@onNull RequestSurfacePackageException error)436         public void onError(@NonNull RequestSurfacePackageException error) {
437             toastAndLog(ERROR, "Failed: %s", error.getMessage());
438         }
439     }
440 
441     private static final class ActivityStarter extends IActivityStarter.Stub {
442         private final Activity mActivity;
443         private final SdkSandboxManager mSdkSandboxManager;
444 
ActivityStarter(Activity activity, SdkSandboxManager manager)445         ActivityStarter(Activity activity, SdkSandboxManager manager) {
446             this.mActivity = activity;
447             this.mSdkSandboxManager = manager;
448         }
449 
450         @Override
startActivity(IBinder token)451         public void startActivity(IBinder token) throws RemoteException {
452             mSdkSandboxManager.startSdkSandboxActivity(mActivity, token);
453         }
454 
455         @Override
toString()456         public String toString() {
457             return mActivity.getComponentName().flattenToShortString();
458         }
459     }
460 
enableStrictMode()461     private void enableStrictMode() {
462         StrictMode.setThreadPolicy(
463                 new StrictMode.ThreadPolicy.Builder()
464                         .detectAll()
465                         .penaltyLog()
466                         .penaltyDeath()
467                         .build());
468         StrictMode.setVmPolicy(
469                 new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build());
470     }
471 }
472