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