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