• 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.AppOwnedSdkSandboxInterface;
34 import android.app.sdksandbox.LoadSdkException;
35 import android.app.sdksandbox.RequestSurfacePackageException;
36 import android.app.sdksandbox.SandboxedSdk;
37 import android.app.sdksandbox.SdkSandboxManager;
38 import android.app.sdksandbox.SdkSandboxManager.SdkSandboxProcessDeathCallback;
39 import android.app.sdksandbox.interfaces.IActivityStarter;
40 import android.app.sdksandbox.interfaces.ISdkApi;
41 import android.content.Context;
42 import android.content.Intent;
43 import android.content.SharedPreferences;
44 import android.content.pm.PackageInfo;
45 import android.content.pm.PackageManager;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.IBinder;
49 import android.os.Looper;
50 import android.os.OutcomeReceiver;
51 import android.os.ParcelFileDescriptor;
52 import android.os.RemoteException;
53 import android.os.StrictMode;
54 import android.text.InputType;
55 import android.util.Log;
56 import android.view.SurfaceControlViewHost.SurfacePackage;
57 import android.view.SurfaceView;
58 import android.view.View;
59 import android.view.ViewGroup;
60 import android.widget.ArrayAdapter;
61 import android.widget.Button;
62 import android.widget.EditText;
63 import android.widget.ImageButton;
64 import android.widget.LinearLayout;
65 import android.widget.Spinner;
66 import android.widget.TextView;
67 
68 import androidx.appcompat.app.AppCompatActivity;
69 import androidx.preference.PreferenceManager;
70 
71 import com.android.modules.utils.BackgroundThread;
72 import com.android.modules.utils.build.SdkLevel;
73 
74 import com.google.android.material.snackbar.BaseTransientBottomBar;
75 import com.google.android.material.snackbar.Snackbar;
76 
77 import java.io.File;
78 import java.io.FileInputStream;
79 import java.io.FileOutputStream;
80 import java.nio.charset.StandardCharsets;
81 import java.util.LinkedList;
82 import java.util.Queue;
83 import java.util.Set;
84 import java.util.Stack;
85 import java.util.concurrent.Executor;
86 import java.util.concurrent.Executors;
87 
88 public class MainActivity extends AppCompatActivity {
89     private static final String SDK_NAME = "com.android.sdksandboxcode";
90     private static final String MEDIATEE_SDK_NAME = "com.android.sdksandboxcode_mediatee";
91     private static final String TAG = "SdkSandboxClientMainActivity";
92 
93     private static final String VIEW_TYPE_KEY = "view-type";
94     private static final String VIDEO_VIEW_VALUE = "video-view";
95     private static final String VIDEO_URL_KEY = "video-url";
96     private static final String VIEW_TYPE_INFLATED_VIEW = "view-type-inflated-view";
97     private static final String VIEW_TYPE_WEBVIEW = "view-type-webview";
98     private static final String VIEW_TYPE_AD_REFRESH = "view-type-ad-refresh";
99     private static final String VIEW_TYPE_EDITTEXT = "view-type-edittext";
100     private static final String IMAGE_VIEW_VALUE = "image-view";
101     private static final String IMAGE_URL_KEY = "image-url";
102 
103     private static final String ON_CLICK_BEHAVIOUR_TYPE_KEY = "on-click-behavior";
104     private static final String ON_CLICK_OPEN_CHROME = "on-click-open-chrome";
105     private static final String ON_CLICK_OPEN_PACKAGE = "on-click-open-package";
106     private static final String PACKAGE_TO_OPEN_KEY = "package-to-open";
107 
108     private static final Handler sHandler = new Handler(Looper.getMainLooper());
109     private static final String EXTRA_SDK_SDK_ENABLED_KEY = "sdkSdkCommEnabled";
110     private static final String DROPDOWN_KEY_SDK_SANDBOX = "SDK_IN_SANDBOX";
111     private static final String DROPDOWN_KEY_SDK_APP = "SDK_IN_APP";
112     private static final String APP_OWNED_SDK_NAME = "app-sdk-1";
113 
114     // Saved instance state keys
115     private static final String SDKS_LOADED_KEY = "sdks_loaded";
116     private static final String APP_OWNED_INTERFACE_REGISTERED = "app-owned-interface_registered";
117     private static final String CUSTOMIZED_SDK_CONTEXT_ENABLED = "customized_sdk_context_enabled";
118     private static final String SANDBOXED_SDK_BINDER = "com.android.sdksandboxclient.SANDBOXED_SDK";
119     private static final String SANDBOXED_SDK_KEY =
120             "com.android.sdksandboxclient.SANDBOXED_SDK_KEY";
121     private static final String DEATH_CALLBACKS_COUNT_KEY =
122             "com.android.sdksandboxclient.DEATH_CALLBACKS_COUNT_KEY";
123     public static final int SNACKBAR_MAX_LINES = 4;
124 
125     private Bundle mSavedInstanceState = new Bundle();
126     private boolean mSdksLoaded = false;
127     private boolean mSdkToSdkCommEnabled = false;
128     private SdkSandboxManager mSdkSandboxManager;
129     private final Executor mExecutor = Executors.newSingleThreadExecutor();
130 
131     private View mRootLayout;
132 
133     private Button mResetPreferencesButton;
134     private Button mLoadSdksButton;
135     private Button mDeathCallbackAddButton;
136     private Button mDeathCallbackRemoveButton;
137     private Button mNewBannerAdButton;
138     private ImageButton mBannerAdOptionsButton;
139     private Button mCreateFileButton;
140     private Button mSyncKeysButton;
141     private Button mSdkToSdkCommButton;
142     private Button mDumpSandboxButton;
143     private Button mNewFullScreenAd;
144     private Button mNewAppWebviewButton;
145     private Button mNewAppVideoButton;
146     private Button mReleaseAllSurfaceControlViewHostButton;
147 
148     private SurfaceView mInScrollBannerView;
149     private SurfaceView mBottomBannerView;
150 
151     private SandboxedSdk mSandboxedSdk;
152     private SharedPreferences mSharedPreferences;
153     private final Stack<SdkSandboxProcessDeathCallback> mDeathCallbacks = new Stack<>();
154     private final Queue<SurfacePackage> mSurfacePackages = new LinkedList<>();
155 
156     @Override
onCreate(Bundle savedInstanceState)157     public void onCreate(Bundle savedInstanceState) {
158         // TODO(b/294188354): This is temporarily disabled to unblock testing. Re-enable later.
159         // enableStrictMode();
160         super.onCreate(savedInstanceState);
161 
162         setAppTitle();
163 
164         mSdkSandboxManager = getApplicationContext().getSystemService(SdkSandboxManager.class);
165         if (savedInstanceState != null) {
166             mSavedInstanceState.putAll(savedInstanceState);
167             mSdksLoaded = savedInstanceState.getBoolean(SDKS_LOADED_KEY);
168             mSandboxedSdk = savedInstanceState.getParcelable(SANDBOXED_SDK_KEY);
169             int numDeathCallbacks = savedInstanceState.getInt(DEATH_CALLBACKS_COUNT_KEY);
170             for (int i = 0; i < numDeathCallbacks; i++) {
171                 addDeathCallback(false);
172             }
173         }
174 
175         mExecutor.execute(
176                 () -> {
177                     Looper.prepare();
178                     mSharedPreferences =
179                             PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
180 
181                     handleExtras();
182                     PreferenceManager.setDefaultValues(this, R.xml.banner_preferences, false);
183                 });
184 
185         setContentView(R.layout.activity_main);
186 
187         mRootLayout = findViewById(R.id.root_layout);
188 
189         mBottomBannerView = findViewById(R.id.bottom_banner_view);
190         mBottomBannerView.setZOrderOnTop(true);
191         mBottomBannerView.setVisibility(View.INVISIBLE);
192 
193         mInScrollBannerView = findViewById(R.id.in_scroll_banner_view);
194         mInScrollBannerView.setZOrderOnTop(true);
195         mInScrollBannerView.setVisibility(View.INVISIBLE);
196 
197         mResetPreferencesButton = findViewById(R.id.reset_preferences_button);
198         mLoadSdksButton = findViewById(R.id.load_sdks_button);
199         mReleaseAllSurfaceControlViewHostButton = findViewById(R.id.release_all_scvh_button);
200 
201         mDeathCallbackAddButton = findViewById(R.id.add_death_callback_button);
202         mDeathCallbackRemoveButton = findViewById(R.id.remove_death_callback_button);
203 
204         mNewBannerAdButton = findViewById(R.id.new_banner_ad_button);
205         mBannerAdOptionsButton = findViewById(R.id.banner_ad_options_button);
206         mNewFullScreenAd = findViewById(R.id.new_fullscreen_ad_button);
207 
208         mCreateFileButton = findViewById(R.id.create_file_button);
209         mSyncKeysButton = findViewById(R.id.sync_keys_button);
210         mSdkToSdkCommButton = findViewById(R.id.enable_sdk_sdk_button);
211         mDumpSandboxButton = findViewById(R.id.dump_sandbox_button);
212         mNewAppWebviewButton = findViewById(R.id.new_app_webview_button);
213         mNewAppVideoButton = findViewById(R.id.new_app_video_button);
214 
215         configureFeatureFlagSection();
216 
217         registerResetPreferencesButton();
218         registerLoadSdksButton();
219         registerReleaseAllSurfaceControlViewHost();
220         registerAddDeathCallbackButton();
221         registerRemoveDeathCallbackButton();
222 
223         registerNewBannerAdButton();
224         registerBannerAdOptionsButton();
225         registerNewFullscreenAdButton();
226 
227         registerGetOrSendFileDescriptorButton();
228         registerCreateFileButton();
229         registerSyncKeysButton();
230         registerSdkToSdkButton();
231         registerDumpSandboxButton();
232         registerNewAppWebviewButton();
233         registerNewAppVideoButton();
234 
235         refreshLoadSdksButtonText();
236     }
237 
238     @Override
onStart()239     public void onStart() {
240         super.onStart();
241         /*
242             Resume video when app is active.
243             TODO (b/314953975) Should be handled on SDK side:
244                 1) (after adding Client App state API): Resume when app is foreground
245                 2) (after migration to ui-lib Visibility): Resume when PlayerView is visible
246         */
247         withSdkApiIfLoaded(ISdkApi::notifyMainActivityStarted);
248     }
249 
250     @Override
onStop()251     public void onStop() {
252         super.onStop();
253         /*
254             Pause video when app is not active.
255             TODO (b/314953975) Should be handled on SDK side:
256                 1) (after adding Client App state API): Pause when app is background
257                 2) (after migration to ui-lib Visibility): Pause when PlayerView is not visible
258         */
259         withSdkApiIfLoaded(ISdkApi::notifyMainActivityStopped);
260     }
261 
registerResetPreferencesButton()262     private void registerResetPreferencesButton() {
263         mResetPreferencesButton.setOnClickListener(
264                 v ->
265                         mExecutor.execute(
266                                 () -> {
267                                     mSharedPreferences.edit().clear().commit();
268                                     PreferenceManager.setDefaultValues(
269                                             this, R.xml.banner_preferences, true);
270                                 }));
271     }
272 
setAppTitle()273     private void setAppTitle() {
274         try {
275             final PackageInfo packageInfo =
276                     getPackageManager().getPackageInfo(getPackageName(), /*flags=*/ 0);
277             final String versionName = packageInfo.versionName;
278             setTitle(
279                     String.format(
280                             "%s (%s)",
281                             getResources().getString(R.string.title_activity_main), versionName));
282         } catch (PackageManager.NameNotFoundException e) {
283             Log.e(TAG, "Could not find package " + getPackageName());
284         }
285     }
286 
handleExtras()287     private void handleExtras() {
288         Bundle extras = getIntent().getExtras();
289         if (extras != null) {
290             final String videoUrl = extras.getString(VIDEO_URL_KEY);
291             mSharedPreferences.edit().putString("banner_video_url", videoUrl).apply();
292             final String packageToOpen = extras.getString(PACKAGE_TO_OPEN_KEY);
293             mSharedPreferences.edit().putString("package_to_open", packageToOpen).apply();
294         }
295     }
296 
configureFeatureFlagSection()297     private void configureFeatureFlagSection() {
298         TextView featureFlagStatus = findViewById(R.id.feature_flags_status);
299         if(!mSdksLoaded) {
300             featureFlagStatus.setText("Load SDK to fetch status of feature flags");
301             return;
302         }
303 
304         if (!mSavedInstanceState.containsKey(CUSTOMIZED_SDK_CONTEXT_ENABLED)) {
305             try {
306                 IBinder binder = mSandboxedSdk.getInterface();
307                 ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
308                 boolean result = sdkApi.isCustomizedSdkContextEnabled();
309                 mSavedInstanceState.putBoolean(CUSTOMIZED_SDK_CONTEXT_ENABLED, result);
310             } catch (RemoteException e) {
311                 logAndDisplayMessage(e, "Failed to fetch feature flag status: %s", e);
312             }
313         }
314 
315         boolean result = mSavedInstanceState.getBoolean(CUSTOMIZED_SDK_CONTEXT_ENABLED);
316         featureFlagStatus.post(
317                 () -> {
318                     featureFlagStatus.setText("CustomizedSdkContext Enabled: " + result);
319                 });
320     }
321 
refreshLoadSdksButtonText()322     private void refreshLoadSdksButtonText() {
323         if (mSdksLoaded) {
324             mLoadSdksButton.post(() -> mLoadSdksButton.setText("Unload SDKs"));
325         } else {
326             mLoadSdksButton.post(() -> mLoadSdksButton.setText("Load SDKs"));
327         }
328     }
329 
330     @Override
onSaveInstanceState(@onNull Bundle outState)331     protected void onSaveInstanceState(@NonNull Bundle outState) {
332         super.onSaveInstanceState(outState);
333         outState.putAll(mSavedInstanceState);
334         outState.putBoolean(SDKS_LOADED_KEY, mSdksLoaded);
335         outState.putParcelable(SANDBOXED_SDK_KEY, mSandboxedSdk);
336         outState.putInt(DEATH_CALLBACKS_COUNT_KEY, mDeathCallbacks.size());
337     }
338 
registerReleaseAllSurfaceControlViewHost()339     private void registerReleaseAllSurfaceControlViewHost() {
340         mReleaseAllSurfaceControlViewHostButton.setOnClickListener(
341                 v -> {
342                     synchronized (mSurfacePackages) {
343                         if (mSurfacePackages.isEmpty()) {
344                             logAndDisplayMessage(INFO, "No SCVH to release.");
345                             return;
346                         }
347                         while (!mSurfacePackages.isEmpty()) {
348                             mSurfacePackages.poll().notifyDetachedFromWindow();
349                         }
350                         mInScrollBannerView.setVisibility(View.INVISIBLE);
351                         mBottomBannerView.setVisibility(View.INVISIBLE);
352                         // TODO (b/314953975) Should be handled in Session.close()
353                         withSdkApiIfLoaded(ISdkApi::notifyMainActivityStopped);
354                         logAndDisplayMessage(INFO, "All SurfaceControlViewHost Released.");
355                     }
356                 });
357     }
358 
registerAddDeathCallbackButton()359     private void registerAddDeathCallbackButton() {
360         mDeathCallbackAddButton.setOnClickListener(
361                 v -> {
362                     synchronized (mDeathCallbacks) {
363                         addDeathCallback(true);
364                     }
365                 });
366     }
367 
registerRemoveDeathCallbackButton()368     private void registerRemoveDeathCallbackButton() {
369         mDeathCallbackRemoveButton.setOnClickListener(
370                 v -> {
371                     synchronized (mDeathCallbacks) {
372                         if (mDeathCallbacks.isEmpty()) {
373                             logAndDisplayMessage(INFO, "No death callbacks to remove.");
374                             return;
375                         }
376                         final int queueSize = mDeathCallbacks.size();
377                         SdkSandboxProcessDeathCallback deathCallback = mDeathCallbacks.pop();
378                         mSdkSandboxManager.removeSdkSandboxProcessDeathCallback(deathCallback);
379                         logAndDisplayMessage(INFO, "Death callback #" + (queueSize) + " removed.");
380                     }
381                 });
382     }
383 
registerLoadSdksButton()384     private void registerLoadSdksButton() {
385         mLoadSdksButton.setOnClickListener(
386                 v -> {
387                     if (mSdksLoaded) {
388                         resetStateForLoadSdkButton();
389                         return;
390                     }
391 
392                     Bundle params = new Bundle();
393                     OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver =
394                             new OutcomeReceiver<>() {
395                                 @Override
396                                 public void onResult(SandboxedSdk sandboxedSdk) {
397                                     mSandboxedSdk = sandboxedSdk;
398                                     IBinder binder = mSandboxedSdk.getInterface();
399                                     ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
400                                     try {
401                                         sdkApi.loadSdkBySdk(MEDIATEE_SDK_NAME);
402                                     } catch (Exception error) {
403                                         logAndDisplayMessage(
404                                                 ERROR, "Failed to load all SDKs: %s", error);
405                                         return;
406                                     }
407                                     logAndDisplayMessage(INFO, "All SDKs Loaded successfully!");
408                                     mSdksLoaded = true;
409                                     refreshLoadSdksButtonText();
410                                     configureFeatureFlagSection();
411                                 }
412 
413                                 @Override
414                                 public void onError(LoadSdkException error) {
415                                     logAndDisplayMessage(
416                                             ERROR, "Failed to load first SDK: %s", error);
417                                 }
418                             };
419                     Log.i(TAG, "Loading SDKs " + SDK_NAME + " and " + MEDIATEE_SDK_NAME);
420                     mSdkSandboxManager.loadSdk(SDK_NAME, params, Runnable::run, receiver);
421                 });
422     }
423 
resetStateForLoadSdkButton()424     private void resetStateForLoadSdkButton() {
425         Log.i(TAG, "Unloading SDKs " + SDK_NAME + " and " + MEDIATEE_SDK_NAME);
426         mSdkSandboxManager.unloadSdk(SDK_NAME);
427         mSdkSandboxManager.unloadSdk(MEDIATEE_SDK_NAME);
428         mSdksLoaded = false;
429         refreshLoadSdksButtonText();
430     }
431 
registerNewBannerAdButton()432     private void registerNewBannerAdButton() {
433         mNewBannerAdButton.setOnClickListener(
434                 v -> {
435                     if (mSdksLoaded) {
436                         final BannerOptions options =
437                                 BannerOptions.fromSharedPreferences(mSharedPreferences);
438                         Log.i(TAG, options.toString());
439 
440                         final SurfaceView surfaceView =
441                                 (options.getPlacement() == BannerOptions.Placement.BOTTOM)
442                                         ? mBottomBannerView
443                                         : mInScrollBannerView;
444 
445                         int adSize = 0;
446                         switch (options.getAdSize()) {
447                             case SMALL:
448                                 {
449                                     adSize = 80;
450                                     break;
451                                 }
452                             case MEDIUM:
453                                 {
454                                     adSize = 150;
455                                     break;
456                                 }
457                             case LARGE:
458                                 {
459                                     adSize = 250;
460                                     break;
461                                 }
462                         }
463                         if (options.getViewType().equals(BannerOptions.ViewType.WEBVIEW)) {
464                             adSize = 400;
465                         }
466                         ViewGroup.LayoutParams svParams = surfaceView.getLayoutParams();
467                         float factor =
468                                 getApplicationContext().getResources().getDisplayMetrics().density;
469                         svParams.height = (int) (adSize * factor);
470                         surfaceView.setLayoutParams(svParams);
471 
472                         final OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver =
473                                 new RequestSurfacePackageReceiver(surfaceView);
474 
475                         final Bundle params = getRequestSurfacePackageParams(null, surfaceView);
476 
477                         switch (options.getViewType()) {
478                             case INFLATED:
479                                 {
480                                     params.putString(VIEW_TYPE_KEY, VIEW_TYPE_INFLATED_VIEW);
481                                     break;
482                                 }
483                             case VIDEO:
484                                 {
485                                     params.putString(VIEW_TYPE_KEY, VIDEO_VIEW_VALUE);
486                                     params.putString(VIDEO_URL_KEY, options.getVideoUrl());
487                                     break;
488                                 }
489                             case WEBVIEW:
490                                 {
491                                     params.putString(VIEW_TYPE_KEY, VIEW_TYPE_WEBVIEW);
492                                     break;
493                                 }
494                             case AD_REFRESH:
495                                 {
496                                     params.putString(VIEW_TYPE_KEY, VIEW_TYPE_AD_REFRESH);
497                                     break;
498                                 }
499                             case EDITTEXT:
500                                 {
501                                     params.putString(VIEW_TYPE_KEY, VIEW_TYPE_EDITTEXT);
502                                     break;
503                                 }
504                             case IMAGE:
505                                 {
506                                     params.putString(VIEW_TYPE_KEY, IMAGE_VIEW_VALUE);
507                                     params.putString(IMAGE_URL_KEY, options.getImageUrl());
508                                     break;
509                                 }
510                         }
511 
512                         switch (options.getOnClick()) {
513                             case OPEN_CHROME:
514                                 {
515                                     params.putString(
516                                             ON_CLICK_BEHAVIOUR_TYPE_KEY, ON_CLICK_OPEN_CHROME);
517                                     break;
518                                 }
519                             case OPEN_PACKAGE:
520                                 {
521                                     params.putString(
522                                             ON_CLICK_BEHAVIOUR_TYPE_KEY, ON_CLICK_OPEN_PACKAGE);
523                                     params.putString(
524                                             PACKAGE_TO_OPEN_KEY, options.getmPackageToOpen());
525                                     break;
526                                 }
527                         }
528                         sHandler.post(
529                                 () -> {
530                                     mSdkSandboxManager.requestSurfacePackage(
531                                             SDK_NAME, params, Runnable::run, receiver);
532                                 });
533                     } else {
534                         logAndDisplayMessage(WARN, "Sdk is not loaded");
535                     }
536                 });
537     }
538 
registerGetOrSendFileDescriptorButton()539     private void registerGetOrSendFileDescriptorButton() {
540         final Button mGetFileDescriptorButton = findViewById(R.id.get_filedescriptor_button);
541         final Button mSendFileDescriptorButton = findViewById(R.id.send_filedescriptor_button);
542         mGetFileDescriptorButton.setOnClickListener(
543                 v -> {
544                     Log.i(TAG, "isGetFileDescriptorCalled = " + String.valueOf(true));
545                     onGetOrSendFileDescriptorPressed(/*isGetFileDescriptorCalled=*/ true);
546                 });
547         mSendFileDescriptorButton.setOnClickListener(
548                 v -> {
549                     Log.i(TAG, "isGetFileDescriptorCalled = " + String.valueOf(false));
550                     onGetOrSendFileDescriptorPressed(/*isGetFileDescriptorCalled=*/ false);
551                 });
552     }
553 
onGetOrSendFileDescriptorPressed(boolean isGetFileDescriptorCalled)554     private void onGetOrSendFileDescriptorPressed(boolean isGetFileDescriptorCalled) {
555         if (!mSdksLoaded) {
556             logAndDisplayMessage(WARN, "Sdk is not loaded");
557             return;
558         }
559         Log.i(TAG, "Ready to transfer File Descriptor between APP and SDK");
560 
561         final AlertDialog.Builder builder = new AlertDialog.Builder(this);
562         builder.setTitle("Set the value for FileDescriptor");
563         final EditText inputValue = new EditText(this);
564         inputValue.setHint("default");
565         builder.setView(inputValue);
566 
567         builder.setPositiveButton(
568                 "Transfer",
569                 (dialog, which) -> {
570                     BackgroundThread.getExecutor()
571                             .execute(
572                                     () -> {
573                                         final String inputValueString =
574                                                 inputValue.getText().toString();
575                                         if (inputValueString.isEmpty()
576                                                 || inputValueString.length() > 1000) {
577                                             logAndDisplayMessage(
578                                                     WARN,
579                                                     "Input string cannot be empty or"
580                                                             + " have more than 1000"
581                                                             + " characters. Try again.");
582                                             return;
583                                         }
584 
585                                         String value;
586                                         if (isGetFileDescriptorCalled) {
587                                             value = onGetFileDescriptorPressed(inputValueString);
588                                         } else {
589                                             value = onSendFileDescriptorPressed(inputValueString);
590                                         }
591 
592                                         String methodName =
593                                                 isGetFileDescriptorCalled
594                                                         ? "getFileDescriptor"
595                                                         : "sendFileDescriptor";
596 
597                                         if (inputValueString.equals(value)) {
598                                             logAndDisplayMessage(
599                                                     INFO,
600                                                     methodName
601                                                             + " transfer successful, value sent"
602                                                             + " = "
603                                                             + inputValueString
604                                                             + " , value received = "
605                                                             + value);
606                                         } else {
607                                             logAndDisplayMessage(
608                                                     WARN,
609                                                     methodName
610                                                             + " transfer unsuccessful, Value"
611                                                             + " sent = "
612                                                             + inputValueString
613                                                             + " , Value received = "
614                                                             + value);
615                                         }
616                                     });
617                 });
618         builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
619         builder.show();
620     }
621 
622     /**
623      * This method receives a fileDescriptor from the SDK and opens it using a file inputstream and
624      * then reads the characters in the file and stores it in a String to return it.
625      */
onGetFileDescriptorPressed(String inputValueString)626     private String onGetFileDescriptorPressed(String inputValueString) {
627         String value;
628         try {
629             IBinder binder = mSandboxedSdk.getInterface();
630             ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
631             ParcelFileDescriptor pFd = sdkApi.getFileDescriptor(inputValueString);
632             FileInputStream fis = new FileInputStream(pFd.getFileDescriptor());
633             // Reading fileInputStream and adding its
634             // value to a string
635             value = new String(fis.readAllBytes(), StandardCharsets.UTF_16);
636             fis.close();
637             pFd.close();
638             return value;
639         } catch (Exception e) {
640             logAndDisplayMessage(ERROR, "Failed to get FileDescriptor: %s", e);
641         }
642         return "";
643     }
644 
645     /**
646      * This method generates a file outputstream in the App and sends the generated FileDescriptor
647      * to SDK to parse it and then receives the parsed value from the SDK and returns it.
648      */
onSendFileDescriptorPressed(String inputValueString)649     private String onSendFileDescriptorPressed(String inputValueString) {
650         try {
651             final String fileName = "testParcelFileDescriptor";
652             FileOutputStream fout =
653                     getApplicationContext().openFileOutput(fileName, Context.MODE_PRIVATE);
654             // Writing inputValue String to a file
655             fout.write(inputValueString.getBytes(StandardCharsets.UTF_16));
656             fout.close();
657             File file = new File(getApplicationContext().getFilesDir(), fileName);
658             ParcelFileDescriptor pFd =
659                     ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
660             IBinder binder = mSandboxedSdk.getInterface();
661             ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
662             String parsedValue = sdkApi.parseFileDescriptor(pFd);
663             pFd.close();
664             return parsedValue;
665         } catch (Exception e) {
666             logAndDisplayMessage(ERROR, "Failed to send FileDescriptor: %s", e);
667         }
668         return "";
669     }
670 
registerBannerAdOptionsButton()671     private void registerBannerAdOptionsButton() {
672         mBannerAdOptionsButton.setOnClickListener(
673                 v -> startActivity(new Intent(MainActivity.this, BannerOptionsActivity.class)));
674     }
675 
registerCreateFileButton()676     private void registerCreateFileButton() {
677         mCreateFileButton.setOnClickListener(
678                 v -> {
679                     if (!mSdksLoaded) {
680                         logAndDisplayMessage(WARN, "Sdk is not loaded");
681                         return;
682                     }
683                     AlertDialog.Builder builder = new AlertDialog.Builder(this);
684                     builder.setTitle("Set size in MB (1-100)");
685                     final EditText input = new EditText(this);
686                     input.setInputType(InputType.TYPE_CLASS_NUMBER);
687                     builder.setView(input);
688                     builder.setPositiveButton(
689                             "Create",
690                             (dialog, which) -> {
691                                 final String inputString = input.getText().toString();
692                                 if (inputString.isEmpty()
693                                         || inputString.length() > 3
694                                         || Integer.parseInt(inputString) <= 0
695                                         || Integer.parseInt(inputString) > 100) {
696                                     logAndDisplayMessage(
697                                             WARN, "Please provide a value between 1 and 100");
698                                     return;
699                                 }
700                                 final Integer sizeInMb = Integer.parseInt(inputString);
701                                 IBinder binder = mSandboxedSdk.getInterface();
702                                 ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
703 
704                                 BackgroundThread.getExecutor()
705                                         .execute(
706                                                 () -> {
707                                                     try {
708                                                         String response =
709                                                                 sdkApi.createFile(sizeInMb);
710                                                         logAndDisplayMessage(INFO, response);
711                                                     } catch (Exception e) {
712                                                         logAndDisplayMessage(
713                                                                 e,
714                                                                 "Failed to create file with %d Mb",
715                                                                 sizeInMb);
716                                                     }
717                                                 });
718                             });
719                     builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
720                     builder.show();
721                 });
722     }
723 
registerSdkToSdkButton()724     private void registerSdkToSdkButton() {
725         mSdkToSdkCommButton.setOnClickListener(
726                 v -> {
727                     mSdkToSdkCommEnabled = !mSdkToSdkCommEnabled;
728                     if (mSdkToSdkCommEnabled) {
729                         mSdkToSdkCommButton.setText("Disable SDK to SDK comm");
730                         logAndDisplayMessage(INFO, "Sdk Sdk Comm Enabled");
731                         AlertDialog.Builder builder = new AlertDialog.Builder(this);
732                         builder.setTitle("Choose winning SDK");
733 
734                         String[] items =
735                                 new String[] {DROPDOWN_KEY_SDK_SANDBOX, DROPDOWN_KEY_SDK_APP};
736                         ArrayAdapter<String> adapter =
737                                 new ArrayAdapter<>(
738                                         this, android.R.layout.simple_spinner_dropdown_item, items);
739                         final Spinner dropdown = new Spinner(this);
740                         dropdown.setAdapter(adapter);
741 
742                         LinearLayout linearLayout = new LinearLayout(this);
743                         linearLayout.setOrientation(1); // 1 is for vertical orientation
744                         linearLayout.addView(dropdown);
745                         builder.setView(linearLayout);
746 
747                         builder.setPositiveButton(
748                                 "Request SP",
749                                 (dialog, which) -> {
750                                     final SurfaceView view = mBottomBannerView;
751                                     OutcomeReceiver<Bundle, RequestSurfacePackageException>
752                                             receiver = new RequestSurfacePackageReceiver(view);
753                                     final String dropDownKey =
754                                             dropdown.getSelectedItem().toString();
755                                     if (dropDownKey.equals(DROPDOWN_KEY_SDK_APP)) {
756                                         if (!mSavedInstanceState.containsKey(
757                                                 APP_OWNED_INTERFACE_REGISTERED)) {
758                                             // Register AppOwnedSdkInterface when activity first
759                                             // created
760                                             // TODO(b/284281064) : We should be checking sdk
761                                             // extension here
762                                             mSdkSandboxManager.registerAppOwnedSdkSandboxInterface(
763                                                     new AppOwnedSdkSandboxInterface(
764                                                             APP_OWNED_SDK_NAME,
765                                                             (long) 1.01,
766                                                             new AppOwnedSdkApi()));
767                                             mSavedInstanceState.putBoolean(
768                                                     APP_OWNED_INTERFACE_REGISTERED, true);
769                                         }
770                                     }
771                                     mSdkSandboxManager.requestSurfacePackage(
772                                             SDK_NAME,
773                                             getRequestSurfacePackageParams(dropDownKey, view),
774                                             Runnable::run,
775                                             receiver);
776                                 });
777                         builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
778                         builder.show();
779                     } else {
780                         mSdkToSdkCommButton.setText("Enable SDK to SDK comm");
781                         logAndDisplayMessage(INFO, "Sdk Sdk Comm Disabled");
782                     }
783                 });
784     }
785 
registerDumpSandboxButton()786     private void registerDumpSandboxButton() {
787         mDumpSandboxButton.setOnClickListener(
788                 v -> {
789                     if (!mSdksLoaded) {
790                         logAndDisplayMessage(WARN, "Sdk is not loaded");
791                         return;
792                     }
793 
794                     IBinder binder = mSandboxedSdk.getInterface();
795                     ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
796                     String sandboxDump = "";
797                     try {
798                         sandboxDump = sdkApi.getSandboxDump();
799                     } catch (RemoteException e) {
800                         // Do nothing, the correct text won't be displayed.
801                     }
802                     new AlertDialog.Builder(this)
803                             .setTitle("Information provided by the sandbox")
804                             .setMessage(sandboxDump)
805                             .setNegativeButton("Cancel", null)
806                             .show();
807                 });
808     }
809 
registerSyncKeysButton()810     private void registerSyncKeysButton() {
811         mSyncKeysButton.setOnClickListener(
812                 v -> {
813                     if (!mSdksLoaded) {
814                         logAndDisplayMessage(WARN, "Sdk is not loaded");
815                         return;
816                     }
817 
818                     final AlertDialog.Builder alert = new AlertDialog.Builder(this);
819 
820                     alert.setTitle("Set the key and value to sync");
821                     LinearLayout linearLayout = new LinearLayout(this);
822                     linearLayout.setOrientation(1); // 1 is for vertical orientation
823                     final EditText inputKey = new EditText(this);
824                     inputKey.setHint("key");
825                     final EditText inputValue = new EditText(this);
826                     inputValue.setHint("value");
827                     linearLayout.addView(inputKey);
828                     linearLayout.addView(inputValue);
829                     alert.setView(linearLayout);
830 
831                     alert.setPositiveButton(
832                             "Sync",
833                             (dialog, which) -> {
834                                 onSyncKeyPressed(inputKey, inputValue);
835                             });
836                     alert.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
837                     alert.show();
838                 });
839     }
840 
onSyncKeyPressed(EditText inputKey, EditText inputValue)841     private void onSyncKeyPressed(EditText inputKey, EditText inputValue) {
842         BackgroundThread.getHandler()
843                 .post(
844                         () -> {
845                             final SharedPreferences pref =
846                                     PreferenceManager.getDefaultSharedPreferences(
847                                             getApplicationContext());
848                             String keyToSync = inputKey.getText().toString();
849                             String valueToSync = inputValue.getText().toString();
850                             pref.edit().putString(keyToSync, valueToSync).commit();
851                             mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of(keyToSync));
852                             IBinder binder = mSandboxedSdk.getInterface();
853                             ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
854                             try {
855                                 // Allow some time for data to sync
856                                 Thread.sleep(1000);
857                                 String syncedKeysValue =
858                                         sdkApi.getSyncedSharedPreferencesString(keyToSync);
859                                 if (syncedKeysValue.equals(valueToSync)) {
860                                     logAndDisplayMessage(
861                                             INFO,
862                                             "Key was synced successfully\n"
863                                                     + "Key is : %s Value is : %s",
864                                             keyToSync,
865                                             syncedKeysValue);
866                                 } else {
867                                     logAndDisplayMessage(WARN, "Key was not synced");
868                                 }
869                             } catch (Exception e) {
870                                 logAndDisplayMessage(e, "Failed to sync keys (%s)", keyToSync);
871                             }
872                         });
873     }
874 
registerNewFullscreenAdButton()875     private void registerNewFullscreenAdButton() {
876         mNewFullScreenAd.setOnClickListener(
877                 v -> {
878                     if (!mSdksLoaded) {
879                         logAndDisplayMessage(WARN, "Sdk is not loaded");
880                         return;
881                     }
882                     if (!SdkLevel.isAtLeastU()) {
883                         logAndDisplayMessage(WARN, "Device should have Android U or above!");
884                         return;
885                     }
886                     IBinder binder = mSandboxedSdk.getInterface();
887                     ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
888                     ActivityStarter starter = new ActivityStarter(this, mSdkSandboxManager);
889 
890                     final BannerOptions options =
891                             BannerOptions.fromSharedPreferences(mSharedPreferences);
892                     Bundle params = new Bundle();
893                     if (options.getViewType() == BannerOptions.ViewType.VIDEO) {
894                         params.putString(VIDEO_URL_KEY, options.getVideoUrl());
895                     }
896                     try {
897                         sdkApi.startActivity(starter, params);
898                         logAndDisplayMessage(INFO, "Started activity %s", starter);
899                     } catch (RemoteException e) {
900                         logAndDisplayMessage(e, "Failed to startActivity (%s)", starter);
901                     }
902                 });
903     }
904 
registerNewAppWebviewButton()905     private void registerNewAppWebviewButton() {
906         mNewAppWebviewButton.setOnClickListener(
907                 v -> {
908                     if (!mSdksLoaded) {
909                         logAndDisplayMessage(WARN, "Sdk is not loaded");
910                         return;
911                     }
912                     IBinder binder = mSandboxedSdk.getInterface();
913                     Intent intent = new Intent(this, AppWebViewActivity.class);
914                     intent.putExtra(SANDBOXED_SDK_BINDER, binder);
915                     startActivity(intent);
916                 });
917     }
918 
registerNewAppVideoButton()919     private void registerNewAppVideoButton() {
920         mNewAppVideoButton.setOnClickListener(
921                 v -> {
922                     final BannerOptions options =
923                             BannerOptions.fromSharedPreferences(mSharedPreferences);
924 
925                     Intent intent = new Intent(this, AppVideoView.class);
926                     intent.putExtra(AppVideoView.VIDEO_URL_KEY, options.getVideoUrl());
927 
928                     startActivity(intent);
929                 });
930     }
931 
getRequestSurfacePackageParams(String commType, SurfaceView surfaceView)932     private Bundle getRequestSurfacePackageParams(String commType, SurfaceView surfaceView) {
933         Bundle params = new Bundle();
934         params.putInt(EXTRA_WIDTH_IN_PIXELS, surfaceView.getWidth());
935         params.putInt(EXTRA_HEIGHT_IN_PIXELS, surfaceView.getLayoutParams().height);
936         params.putInt(EXTRA_DISPLAY_ID, getDisplay().getDisplayId());
937         params.putBinder(EXTRA_HOST_TOKEN, surfaceView.getHostToken());
938         params.putString(EXTRA_SDK_SDK_ENABLED_KEY, commType);
939         return params;
940     }
941 
logAndDisplayMessage(int logLevel, String fmt, Object... args)942     private void logAndDisplayMessage(int logLevel, String fmt, Object... args) {
943         String message = String.format(fmt, args);
944         switch (logLevel) {
945             case DEBUG:
946                 Log.d(TAG, message);
947                 break;
948             case ERROR:
949                 Log.e(TAG, message);
950                 break;
951             case INFO:
952                 Log.i(TAG, message);
953                 break;
954             case VERBOSE:
955                 Log.v(TAG, message);
956                 break;
957             case WARN:
958                 Log.w(TAG, message);
959                 break;
960             default:
961                 Log.w(TAG, "Invalid log level " + logLevel + " for message: " + message);
962         }
963         displayMessage(message);
964     }
965 
logAndDisplayMessage(Exception e, String fmt, Object... args)966     private void logAndDisplayMessage(Exception e, String fmt, Object... args) {
967         String message = String.format(fmt, args);
968         Log.e(TAG, message, e);
969         displayMessage(message);
970     }
971 
displayMessage(CharSequence message)972     private void displayMessage(CharSequence message) {
973         runOnUiThread(
974                 () -> {
975                     final Snackbar snackbar =
976                             Snackbar.make(mRootLayout, message, Snackbar.LENGTH_LONG);
977                     snackbar.setAction(R.string.snackbar_dismiss, v -> snackbar.dismiss());
978                     snackbar.setTextMaxLines(SNACKBAR_MAX_LINES);
979                     snackbar.addCallback(
980                             new BaseTransientBottomBar.BaseCallback<>() {
981                                 @Override
982                                 public void onDismissed(Snackbar transientBottomBar, int event) {
983                                     mBottomBannerView.setZOrderOnTop(true);
984                                 }
985                             });
986 
987                     mBottomBannerView.setZOrderOnTop(false);
988                     snackbar.show();
989                 });
990     }
991 
addDeathCallback(boolean notifyAdded)992     private void addDeathCallback(boolean notifyAdded) {
993         final int queueSize = mDeathCallbacks.size();
994         SdkSandboxProcessDeathCallback deathCallback =
995                 () ->
996                         logAndDisplayMessage(
997                                 INFO, "Death callback #" + (queueSize + 1) + " notified.");
998         mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, deathCallback);
999         mDeathCallbacks.add(deathCallback);
1000         if (notifyAdded) {
1001             logAndDisplayMessage(INFO, "Death callback # " + (queueSize + 1) + " added.");
1002         }
1003     }
1004 
1005     private class RequestSurfacePackageReceiver
1006             implements OutcomeReceiver<Bundle, RequestSurfacePackageException> {
1007 
1008         private final SurfaceView mSurfaceView;
1009 
RequestSurfacePackageReceiver(SurfaceView surfaceView)1010         private RequestSurfacePackageReceiver(SurfaceView surfaceView) {
1011             mSurfaceView = surfaceView;
1012         }
1013 
1014         @Override
onResult(Bundle result)1015         public void onResult(Bundle result) {
1016             sHandler.post(
1017                     () -> {
1018                         SurfacePackage surfacePackage =
1019                                 result.getParcelable(EXTRA_SURFACE_PACKAGE, SurfacePackage.class);
1020                         mSurfaceView.setChildSurfacePackage(surfacePackage);
1021                         mSurfacePackages.add(surfacePackage);
1022                         mSurfaceView.setVisibility(View.VISIBLE);
1023                     });
1024             logAndDisplayMessage(INFO, "Rendered surface view");
1025         }
1026 
1027         @Override
onError(@onNull RequestSurfacePackageException error)1028         public void onError(@NonNull RequestSurfacePackageException error) {
1029             logAndDisplayMessage(ERROR, "Failed: %s", error.getMessage());
1030         }
1031     }
1032 
1033     private static final class ActivityStarter extends IActivityStarter.Stub {
1034         private final Activity mActivity;
1035         private final SdkSandboxManager mSdkSandboxManager;
1036 
ActivityStarter(Activity activity, SdkSandboxManager manager)1037         ActivityStarter(Activity activity, SdkSandboxManager manager) {
1038             this.mActivity = activity;
1039             this.mSdkSandboxManager = manager;
1040         }
1041 
1042         @Override
startActivity(IBinder token)1043         public void startActivity(IBinder token) throws RemoteException {
1044             mSdkSandboxManager.startSdkSandboxActivity(mActivity, token);
1045         }
1046 
1047         @Override
toString()1048         public String toString() {
1049             return mActivity.getComponentName().flattenToShortString();
1050         }
1051     }
1052 
enableStrictMode()1053     private void enableStrictMode() {
1054         StrictMode.setThreadPolicy(
1055                 new StrictMode.ThreadPolicy.Builder()
1056                         .detectAll()
1057                         .penaltyLog()
1058                         .penaltyDeath()
1059                         .build());
1060         StrictMode.setVmPolicy(
1061                 new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build());
1062     }
1063 
1064     private interface ConsumerWithException<T> {
accept(T t)1065         void accept(T t) throws Exception;
1066     }
1067 
withSdkApiIfLoaded(ConsumerWithException<ISdkApi> sdkApiConsumer)1068     private void withSdkApiIfLoaded(ConsumerWithException<ISdkApi> sdkApiConsumer) {
1069         if (!mSdksLoaded) {
1070             return;
1071         }
1072         final IBinder binder = mSandboxedSdk.getInterface();
1073         final ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
1074         if (sdkApi == null) {
1075             logAndDisplayMessage(ERROR, "Failed to get SdkApi: Invalid SDK object");
1076             return;
1077         }
1078         try {
1079             sdkApiConsumer.accept(sdkApi);
1080         } catch (Exception error) {
1081             logAndDisplayMessage(ERROR, "Exception while calling SdkApi: %s", error);
1082         }
1083     }
1084 }
1085