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.sdksandboxcode_1;
18 
19 import android.app.sdksandbox.AppOwnedSdkSandboxInterface;
20 import android.app.sdksandbox.SandboxedSdk;
21 import android.app.sdksandbox.SandboxedSdkProvider;
22 import android.app.sdksandbox.interfaces.IMediateeSdkApi;
23 import android.app.sdksandbox.sdkprovider.SdkSandboxController;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.Paint;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.IBinder;
32 import android.os.RemoteException;
33 import android.text.StaticLayout;
34 import android.text.TextPaint;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.webkit.WebSettings;
40 import android.webkit.WebView;
41 import android.widget.EditText;
42 import android.widget.RelativeLayout;
43 import android.widget.TextView;
44 import android.widget.Toast;
45 
46 import java.util.List;
47 import java.util.Random;
48 
49 public class SampleSandboxedSdkProvider extends SandboxedSdkProvider {
50 
51     private static final String TAG = "SampleSandboxedSdkProvider";
52 
53     private static final String VIEW_TYPE_KEY = "view-type";
54     private static final String VIDEO_VIEW_VALUE = "video-view";
55     private static final String VIEW_TYPE_INFLATED_VIEW = "view-type-inflated-view";
56     private static final String VIEW_TYPE_WEBVIEW = "view-type-webview";
57     private static final String VIEW_TYPE_AD_REFRESH = "view-type-ad-refresh";
58     private static final String VIEW_TYPE_EDITTEXT = "view-type-edittext";
59     private static final String VIDEO_URL_KEY = "video-url";
60     private static final String EXTRA_SDK_SDK_ENABLED_KEY = "sdkSdkCommEnabled";
61     private static final String APP_OWNED_SDK_NAME = "app-sdk-1";
62     private static final String ON_CLICK_BEHAVIOUR_TYPE_KEY = "on-click-behavior";
63     private static final String ON_CLICK_OPEN_CHROME = "on-click-open-chrome";
64     private static final String ON_CLICK_OPEN_PACKAGE = "on-click-open-package";
65     private static final String PACKAGE_TO_OPEN_KEY = "package-to-open";
66     private final PlayerViewProvider mPlayerViewProvider = new PlayerViewProvider();
67     private String mSdkSdkCommEnabled = null;
68     private String mOnClickOpenPackage = null;
69 
70     @Override
onLoadSdk(Bundle params)71     public SandboxedSdk onLoadSdk(Bundle params) {
72         return new SandboxedSdk(new SdkApi(getContext(), mPlayerViewProvider));
73     }
74 
75     @Override
beforeUnloadSdk()76     public void beforeUnloadSdk() {
77         mPlayerViewProvider.onHostActivityStopped();
78         Log.i(TAG, "SDK unloaded");
79     }
80 
81     @Override
getView(Context windowContext, Bundle params, int width, int height)82     public View getView(Context windowContext, Bundle params, int width, int height) {
83         if (params.getString(ON_CLICK_BEHAVIOUR_TYPE_KEY, "").equals(ON_CLICK_OPEN_PACKAGE)) {
84             mOnClickOpenPackage = params.getString(PACKAGE_TO_OPEN_KEY, "");
85         }
86         String type = params.getString(VIEW_TYPE_KEY, "");
87         if (VIDEO_VIEW_VALUE.equals(type)) {
88             String videoUrl = params.getString(VIDEO_URL_KEY, "");
89             return mPlayerViewProvider.createPlayerView(windowContext, videoUrl);
90         } else if (VIEW_TYPE_INFLATED_VIEW.equals(type)) {
91             final LayoutInflater inflater =
92                     (LayoutInflater)
93                             windowContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
94             View view = inflater.inflate(R.layout.sample_layout, null);
95             view.setOnClickListener(getOnClickListener(getContext()));
96             return view;
97         } else if (VIEW_TYPE_WEBVIEW.equals(type)) {
98             return new TestWebView(windowContext);
99         } else if (VIEW_TYPE_AD_REFRESH.equals(type)) {
100             final LayoutInflater inflater =
101                     (LayoutInflater)
102                             windowContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
103             final View view = inflater.inflate(R.layout.sample_layout, null);
104             view.setOnClickListener(getOnClickListener(getContext()));
105             view.postDelayed(
106                     () -> {
107                         final Random random = new Random();
108                         final TextView textView = (TextView) view.findViewById(R.id.textViewItem);
109                         textView.setBackgroundColor(
110                                 Color.rgb(
111                                         random.nextInt(256),
112                                         random.nextInt(256),
113                                         random.nextInt(256)));
114 
115                         view.postDelayed(
116                                 () -> {
117                                     final RelativeLayout rl =
118                                             (RelativeLayout) view.findViewById(R.id.item);
119                                     rl.setBackgroundColor(
120                                             Color.rgb(
121                                                     random.nextInt(256),
122                                                     random.nextInt(256),
123                                                     random.nextInt(256)));
124                                 },
125                                 2000);
126                     },
127                     1000);
128 
129             return view;
130         } else if (VIEW_TYPE_EDITTEXT.equals(type)) {
131             EditText editText = new EditText(windowContext);
132             editText.setWidth(width);
133             editText.setHeight(height);
134             editText.setBackgroundColor(Color.BLUE);
135             editText.setTextColor(Color.WHITE);
136             editText.setText("Enter text: ");
137             return editText;
138         }
139         mSdkSdkCommEnabled = params.getString(EXTRA_SDK_SDK_ENABLED_KEY, null);
140 
141         return new TestView(
142                 windowContext,
143                 getContext(),
144                 mSdkSdkCommEnabled,
145                 getOnClickListener(getContext()),
146                 width);
147     }
148 
getOnClickListener(Context context)149     protected View.OnClickListener getOnClickListener(Context context) {
150         if (mOnClickOpenPackage == null) {
151             return new OpenBrowserOnClickListener(context);
152         } else return new OpenPackageOnClickListener(context, mOnClickOpenPackage);
153     }
154 
155     private static class TestView extends View {
156 
157         private static final CharSequence MEDIATEE_SDK = "com.android.sdksandboxcode_mediatee";
158         private static final String DROPDOWN_KEY_SDK_SANDBOX = "SDK_IN_SANDBOX";
159         private static final String DROPDOWN_KEY_SDK_APP = "SDK_IN_APP";
160         private Context mSdkContext;
161         private String mSdkToSdkCommEnabled;
162         private View.OnClickListener mClickListener;
163         private int mWidth;
164 
TestView( Context windowContext, Context sdkContext, String sdkSdkCommEnabled, View.OnClickListener clickListener, int width)165         TestView(
166                 Context windowContext,
167                 Context sdkContext,
168                 String sdkSdkCommEnabled,
169                 View.OnClickListener clickListener,
170                 int width) {
171             super(windowContext);
172             mSdkContext = sdkContext;
173             mSdkToSdkCommEnabled = sdkSdkCommEnabled;
174             mClickListener = clickListener;
175             mWidth = width;
176         }
177 
178         @Override
onDraw(Canvas canvas)179         protected void onDraw(Canvas canvas) {
180             super.onDraw(canvas);
181 
182             Random random = new Random();
183             String message = null;
184 
185             if (!TextUtils.isEmpty(mSdkToSdkCommEnabled)) {
186                 if (mSdkToSdkCommEnabled.equals(DROPDOWN_KEY_SDK_SANDBOX)) {
187                     SandboxedSdk mediateeSdk;
188                     try {
189                         // get message from another sandboxed SDK
190                         List<SandboxedSdk> sandboxedSdks =
191                                 mSdkContext
192                                         .getSystemService(SdkSandboxController.class)
193                                         .getSandboxedSdks();
194                         mediateeSdk =
195                                 sandboxedSdks.stream()
196                                         .filter(
197                                                 s ->
198                                                         s.getSharedLibraryInfo()
199                                                                 .getName()
200                                                                 .contains(MEDIATEE_SDK))
201                                         .findAny()
202                                         .get();
203                     } catch (Exception e) {
204                         throw new IllegalStateException("Error in sdk-sdk communication ", e);
205                     }
206                     try {
207                         IBinder binder = mediateeSdk.getInterface();
208                         IMediateeSdkApi sdkApi = IMediateeSdkApi.Stub.asInterface(binder);
209                         message = sdkApi.getMessage();
210                     } catch (RemoteException e) {
211                         throw new IllegalStateException(e);
212                     }
213                 } else if (mSdkToSdkCommEnabled.equals(DROPDOWN_KEY_SDK_APP)) {
214                     try {
215                         // get message from an app owned SDK
216                         List<AppOwnedSdkSandboxInterface> appOwnedSdks =
217                                 mSdkContext
218                                         .getSystemService(SdkSandboxController.class)
219                                         .getAppOwnedSdkSandboxInterfaces();
220 
221                         AppOwnedSdkSandboxInterface appOwnedSdk =
222                                 appOwnedSdks.stream()
223                                         .filter(s -> s.getName().contains(APP_OWNED_SDK_NAME))
224                                         .findAny()
225                                         .get();
226                         IMediateeSdkApi appOwnedSdkApi =
227                                 IMediateeSdkApi.Stub.asInterface(appOwnedSdk.getInterface());
228                         message = appOwnedSdkApi.getMessage();
229                     } catch (RemoteException e) {
230                         throw new IllegalStateException(e);
231                     }
232                 }
233             } else {
234                 message = mSdkContext.getResources().getString(R.string.view_message);
235             }
236             int c = Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256));
237             canvas.drawColor(c);
238 
239             TextPaint paint = new TextPaint();
240             paint.setStyle(Paint.Style.FILL);
241             paint.setColor(Color.WHITE);
242             paint.setTextSize(50);
243             StaticLayout.Builder.obtain(message, 0, message.length(), paint, mWidth)
244                     .build()
245                     .draw(canvas);
246 
247             setOnClickListener(mClickListener);
248         }
249     }
250 
251     private static class OpenBrowserOnClickListener implements View.OnClickListener {
252 
253         private final Context mSdkContext;
254 
OpenBrowserOnClickListener(Context sdkContext)255         private OpenBrowserOnClickListener(Context sdkContext) {
256             mSdkContext = sdkContext;
257         }
258 
259         @Override
onClick(View view)260         public void onClick(View view) {
261             Context context = view.getContext();
262             Toast.makeText(context, "Opening url", Toast.LENGTH_LONG).show();
263 
264             String url = "http://www.google.com";
265             Intent visitUrl = new Intent(Intent.ACTION_VIEW);
266             visitUrl.setData(Uri.parse(url));
267             visitUrl.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
268 
269             mSdkContext.startActivity(visitUrl);
270         }
271 
272     }
273 
274     private static class OpenPackageOnClickListener implements View.OnClickListener {
275 
276         private final Context mSdkContext;
277         private final String mPackageToOpen;
278 
OpenPackageOnClickListener(Context sdkContext, String packageToOpen)279         private OpenPackageOnClickListener(Context sdkContext, String packageToOpen) {
280             mSdkContext = sdkContext;
281             mPackageToOpen = packageToOpen;
282         }
283 
284         @Override
onClick(View view)285         public void onClick(View view) {
286             Context context = view.getContext();
287             Intent intent =
288                     mSdkContext.getPackageManager().getLaunchIntentForPackage(mPackageToOpen);
289             if (intent != null) {
290                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
291                 mSdkContext.startActivity(intent);
292             } else {
293                 Log.i(TAG, "Opening the Play store.");
294                 intent = new Intent(Intent.ACTION_VIEW);
295                 intent.setData(
296                         Uri.parse(
297                                 "https://play.google.com/store/apps/details?id=" + mPackageToOpen));
298                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
299                 mSdkContext.startActivity(intent);
300             }
301         }
302     }
303 
304     private static class TestWebView extends WebView {
TestWebView(Context windowContext)305         TestWebView(Context windowContext) {
306             super(windowContext);
307             initializeSettings(getSettings());
308             loadUrl("https://www.google.com/");
309         }
310 
initializeSettings(WebSettings settings)311         private void initializeSettings(WebSettings settings) {
312             settings.setJavaScriptEnabled(true);
313             settings.setGeolocationEnabled(true);
314             settings.setSupportZoom(true);
315             settings.setDatabaseEnabled(true);
316             settings.setDomStorageEnabled(true);
317             settings.setAllowFileAccess(true);
318             settings.setAllowContentAccess(true);
319             settings.setUseWideViewPort(true);
320             settings.setLoadWithOverviewMode(true);
321             settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING);
322         }
323     }
324 }
325