SDK ランタイムを使用すると、呼び出し元アプリとは別の専用のサンドボックスで SDK を実行でき、ユーザーデータの収集に関する安全性と確実性が向上します。SDK ランタイムは、実行環境を変更してデータアクセス権と許可された権限セットを制限することでこれを実現しています。SDK ランタイムの詳細については設計案をご覧ください。
このページでは、呼び出し元アプリにリモートでレンダリングできるウェブベースのビューを定義するランタイム対応の SDK を作成する手順について説明します。
始める前に
まず、次の手順を実施します。
- Android 版プライバシー サンドボックス用に開発環境をセットアップします。
- サポート対象のデバイスにシステム イメージをインストールするか、Android 版プライバシー サンドボックスのサポートを含むエミュレータをセットアップします。
Android Studio でプロジェクトをセットアップする
SDK ランタイムを試すには、クライアント サーバー モデルに似たモデルを使用します。主な違いは、アプリ(クライアント)と SDK(サーバー)が同じデバイス上で実行されることです。
プロジェクトに、アプリ モジュールと SDK モジュールを 1 つずつ追加します。これにより、コードを並べて実行してテストしやすくなります。アプリコードと SDK コードの両方が確実に分離されるように、2 つの別々のアプリを作成する必要があります。
SDK デベロッパーなのかアプリ デベロッパーなのかによって、最終的なセットアップが前の段落の内容と異なる場合があります。
アプリをインストールする場合と同様に、Android Studio または Android Debug Bridge(ADB)を使用してテストデバイスに SDK をインストールします。
簡単に始められるよう、Kotlin と Java プログラミング言語を使用して作成したサンプルアプリを GitHub リポジトリで公開しています。
SDK を準備する
次のコード スニペットに示すように、SDK アプリの AndroidManifest.xml
ファイルの <application>
セクションに、<sdk-library>
要素と <property>
要素を追加します。ランタイム対応 SDK に一意の名前を使用し、バージョンを指定します。
<application ...> <sdk-library android:name="com.example.privacysandbox.provider" android:versionMajor="1" /> <property android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME" android:value="com.example.provider.SdkProviderImpl" /> </application>
SDK のエントリ ポイントは SandboxedSdkProvider
を拡張します。SDK アプリをコンパイルするには、SDK ライフサイクルを処理するメソッドをオーバーライドする必要があります。
initSdk()
- SDK を初期化します。初期化が完了して使用できる状態になったら呼び出し元アプリに通知します。
getView()
- 広告のビューを作成してセットアップし、他の Android ビューと同じように初期化して、リモート レンダリング用のビューを返します。
onDataReceived()
sendData()
を使用して、ホストアプリから送信されたメタデータを処理します。
次のコード スニペットは、これらのメソッドをオーバーライドする方法を示しています。
Kotlin
class SdkProviderImpl : SandboxedSdkProvider() { override fun initSdk( sandboxedSdkContext: SandboxedSdkContext, params: Bundle, executor: Executor, initSdkCallback: InitSdkCallback ) { // Update the callback with optional data to show that initialization // is complete. executor.execute { initSdkCallback.onInitSdkFinished(Bundle()) } } override fun getView(windowContext: Context, bundle: Bundle): View { val webView = WebView(windowContext) webView.loadUrl("https://developer.android.com/privacy-sandbox") return webView } override fun onDataReceived(bundle: Bundle, dataReceivedCallback: DataReceivedCallback) { // Update the callback with optional data to show that the receiving // or processing of data is complete dataReceivedCallback.onDataReceivedSuccess(Bundle()) } }
Java
public class SdkProviderImpl extends SandboxedSdkProvider { @Override public void initSdk(SandboxedSdkContext sandboxedSdkContext, Bundle params, Executor executor, InitSdkCallback initSdkCallback) { // Update the callback with optional data to show that the // initialization is complete. executor.execute(() -> initSdkCallback.onInitSdkFinished(new Bundle())); } @Override public View getView(Context windowContext, Bundle bundle) { WebView webView = new WebView(windowContext); webView.loadUrl("https://developer.android.com/privacy-sandbox"); return webView; } @Override public void onDataReceived(Bundle bundle, DataReceivedCallback callback) { // Update the callback with optional data to show that the receiving or // processing of data is complete dataReceivedCallback.onDataReceivedSuccess(new Bundle()); } }
SDK ランタイムで動画プレーヤーをテストする
バナー広告のサポートに加え、プライバシー サンドボックスは、SDK ランタイム内で動作する動画プレーヤーのサポートも行っています。
動画プレーヤーをテストするフローは、バナー広告をテストする場合と似ています。返される View
オブジェクトに動画プレーヤーが含まれるように、SDK のエントリ ポイントの getView()
メソッドを変更します。プライバシー サンドボックスでサポートされると思われる動画プレーヤーのフローをすべてテストします。なお、動画のライフサイクルに関する SDK とクライアント アプリの間の通信は現在のところ対象範囲外であるため、フィードバックは必要ありません。
テストとフィードバックを行うことで、お使いの動画プレーヤーのユースケースがすべて SDK ランタイムでサポートされるようになります。
次のコード スニペットは、URL から読み込むシンプルな動画ビューを返す方法を示しています。
Kotlin
class SdkProviderImpl : SandboxedSdkProvider() {
override fun getView(windowContext: Context, params: Bundle): View {
val videoView = VideoView(windowContext)
videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"))
videoView.setOnPreparedListener { mp -> mp.start() }
return videoView
}
}
Java
public class SdkProviderImpl extends SandboxedSdkProvider {
@Override
public View getView(Context windowContext, Bundle params) {
VideoView videoView = new VideoView(windowContext);
videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"));
videoView.setOnPreparedListener(mp -> {
mp.start();
});
return videoView;
}
}
SDK でのストレージ API の使用
SDK ランタイムの SDK は、アプリの内部ストレージに対するアクセス、読み取り、書き込みができなくなります。その逆も同様です。SDK ランタイムには独自の内部ストレージ領域が割り当てられるため、アプリから切り離されます。
各 SDK ランタイムの個別の内部ストレージ内で、各 SDK には、SDK ごとのストレージという独自のストレージ ディレクトリが提供されます。SDK ごとのストレージは、SDK ランタイムの内部ストレージを論理的に分離したものです。これにより、各 SDK が使用するストレージの量を確保できます。
各 SDK は、initSdk()
メソッドで受け取る SandboxedSdkContext
オブジェクトのファイル ストレージ API を使用して、SDK ごとのストレージを使用できます。SDK ごとのストレージは、クライアント アプリをアンインストールするかクライアント アプリのデータをクリーンアップするまで、保持されます。
SDK は内部ストレージのみを使用できます。そのため、SandboxedSdkContext.getFilesDir()
や SandboxedSdkContext.getCacheDir()
などの内部ストレージ API だけが機能します。その他の例については、内部ストレージからアクセスするをご覧ください。
SDK ランタイムから外部ストレージへのアクセスはサポートされていません。API を呼び出して外部ストレージにアクセスすると、例外がスローされるか、null
が返されます。次に例を示します。
- ストレージ アクセス フレームワークを使用してファイルにアクセスすると、
SecurityException
がスローされます。 getExternalFilsDir()
は常にnull
を返します。
ストレージには、指定の SandboxedSdkContext
を使用する必要があります。アプリ コンテキストなど、他の Context
オブジェクト インスタンスでファイル ストレージ API を使用すると、SDK ごとの内部ストレージは利用されません。あらゆる状況または将来において動作が保証されるわけではありません。
次のコード スニペットは、SDK ランタイムでストレージを使用する方法を示しています。
Java
public class SdkProviderImpl extends SandboxedSdkProvider {
private SandboxedSdkContext mContext;
@Override
public void initSdk(SandboxedSdkContext sandboxedSdkContext, Bundle params,
Executor executor, InitSdkCallback initSdkCallback) {
// Store reference to the SandboxedSdkContext for later usage
mContext = sandboxedSdkContext;
}
@Override
public void onDataReceived(Bundle data, DataReceivedCallback callback) {
// Use the SandboxedSdkContext to use storage
final filename = "extraData";
final String fileContents = data.getString("extraData", "");
try (FileOutputStream fos = mContext.openFileOutput(filename, Context.MODE_PRIVATE)) {
fos.write(fileContents.toByteArray());
callback.onDataReceivedSuccess();
} catch (Exception e) {
callback.onDataReceivedError("Unable to process data.");
}
}
}
Kotlin
class SdkProviderImpl : SandboxedSdkProvider() {
private lateinit var mContext: SandboxedSdkContext
override fun initSdk(
sandboxedSdkContext: SandboxedSdkContext, params: Bundle,
executor: Executor, initSdkCallback: InitSdkCallback
) {
// Store reference to the SandboxedContext for later usage
mContext = sandboxedSdkContext;
}
override fun onDataReceived(data: Bundle, callback: DataReceivedCallback) {
// Use the SandboxedSdkContext to use storage
val filename = "myfile"
val fileContents = data.getString("extraData", "")
try {
mContext.openFileOutput(filename, Context.MODE_PRIVATE).use {
it.write(fileContents.toByteArray())
callback.onDataReceivedSuccess()
} catch (e: Exception) {
callback.onDataReceivedError("Unable to process data.");
}
}
}
クライアント アプリを更新する
SDK ランタイムで実行されている SDK を呼び出すには、呼び出し元のクライアント アプリを次のように変更します。
広告を含むアプリのアクティビティで、
SdkSandboxManager
への参照、SDK が読み込まれているかどうかを示すブール値、リモート レンダリング用のSurfaceView
オブジェクトを宣言します。Kotlin
private lateinit var mSdkSandboxManager: SdkSandboxManager private lateinit var mClientView: SurfaceView private var mSdkLoaded = false companion object { private const val SDK_NAME = "com.example.privacysandbox.provider" }
Java
private static final String SDK_NAME = "com.example.privacysandbox.provider"; private SdkSandboxManager mSdkSandboxManager; private SurfaceView mClientView; private boolean mSdkLoaded = false;
LoadSdkCallback
を実装することでコールバック クラスを定義し、ランタイムで SDK を操作します。Kotlin
private inner class RemoteSdkCallbackImpl() : RemoteSdkCallback { override fun onLoadSdkSuccess(params: Bundle) { mSdkLoaded = true } override fun onLoadSdkFailure(errorCode: Int, errorMessage: String) { // log/show error } }
Java
private class RemoteSdkCallbackImpl implements RemoteSdkCallback { private RemoteSdkCallbackImpl() {} @Override public void onLoadSdkSuccess(Bundle params) { mSdkLoaded = true; } @Override public void onLoadSdkFailure(int errorCode, String errorMessage) { // log/show error } }
requestSurfacePackage()
を呼び出しつつランタイムで SDK からリモートビューを取得するには、コールバック クラス RequestSurfacePackageCallback
を定義します。
Kotlin
private inner class RequestSurfacePackageCallbackImpl() : RequestSurfacePackageCallback {
// Loads the remote view specified in the SDK's getView() method.
override fun onSurfacePackageReady(
surfacePackage: SurfacePackage,
surfacePackageId: Int, params: Bundle
) {
Handler(Looper.getMainLooper()).post {
mClientView.setChildSurfacePackage(surfacePackage)
mClientView.visibility = View.VISIBLE
}
}
override fun onSurfacePackageError(errorCode: Int, errorMessage: String) {
// log/show error
}
}
Java
private class RequestSurfacePackageCallbackImpl implements RequestSurfacePackageCallback {
// Loads the remote view specified in the SDK's getView() method.
@Override
public void onSurfacePackageReady(SurfacePackage surfacePackage,
int surfacePackageId, Bundle params) {
new Handler(Looper.getMainLooper()).post(() -> {
mClientView.setChildSurfacePackage(surfacePackage);
mClientView.setVisibility(View.VISIBLE);
});
}
@Override
public void onSurfacePackageError(int errorCode, String errorMessage) {
// log/show error
}
}
sendData()
のステータスをトラッキングし、SDK から返されたデータを取得するために、コールバック クラス SendDataCallback
を定義します。
Kotlin
private inner class SendDataCallbackImpl() : SendDataCallback {
override fun onSendDataSuccess(
params: Bundle
) {
// Use the data returned by the SDK if required.
}
override fun onSendDataError(errorCode: Int, errorMessage: String) {
// log/show error
}
}
Java
private class SendDataCallbackImpl implements SendDataCallback {
@Override
public void onSendDataSuccess(Bundle params) {
// Use the data returned by the SDK if required.
}
@Override
public void onSendDataError(int errorCode, String errorMessage) {
// log/show error
}
}
onCreate()
で、SdkSandboxManager
と必要なコールバックを初期化し、リモートビューを表示するようにリクエストを行います。Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mSdkSandboxManager = applicationContext.getSystemService( SdkSandboxManager::class.java ) mClientView = findViewById(R.id.rendered_view) mClientView.setZOrderOnTop(true) val loadSdkCallback = LoadSdkCallbackImpl() mSdkSandboxManager.loadSdk( SDK_NAME, Bundle(), { obj: Runnable -> obj.run() }, loadSdkCallback ) val sendDataCallback = SendDataCallback() // Send some data to the SDK if needed mSdkSandboxManager.sendData(SDK_NAME, Bundle(),{ obj: Runnable -> obj.run() }, sendDataCallback) val requestSurfacePackageCallback = RequestSurfacePackageCallback() Handler(Looper.getMainLooper()).post { val bundle = Bundle() mSdkSandboxManager.requestSurfacePackage( SDK_NAME, display!!.displayId, mClientView.width, mClientView.height, bundle, requestSurfacePackageCallback ) } }
Java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSdkSandboxManager = getApplicationContext().getSystemService( SdkSandboxManager.class); mClientView = findViewById(R.id.rendered_view); mClientView.setZOrderOnTop(true); LoadSdkCallbackImpl loadSdkCallback = new LoadSdkCallbackImpl(); mSdkSandboxManager.loadSdk( SDK_NAME, new Bundle(), Runnable::run, loadSdkCallback); SendDataCallback sendDataCallback = new SendDataCallback(); // Send some data to the SDK if needed mSdkSandboxManager.sendData(SDK_NAME, new Bundle(), Runnable::run, sendDataCallback); RequestSurfacePackageCallback requestSurfacePackageCallback = new RequestSurfacePackageCallback(); new Handler(Looper.getMainLooper()).post(() -> { Bundle bundle = new Bundle(); mSdkSandboxManager.requestSurfacePackage( SDK_NAME, getDisplay().getDisplayId(), mClientView.getWidth(), mClientView.getHeight(), bundle, requestSurfacePackageCallback); }); }
証明書ダイジェストを手動で指定します。証明書ダイジェストを確認するには、
keytool
を使用してデバッグ キーストア ファイルから抽出します。デフォルトのパスワードはandroid
です。keytool -list -keystore ~/.android/debug.keystore
アプリのマニフェスト ファイルの
<application>
要素に<uses-sdk-library>
要素を追加します。この要素内で、android:certDigest
属性を前のステップの出力に設定します。<application ...> <uses-sdk-library android:name="com.example.basicsandboxservice" android:versionMajor="1" android:certDigest="27:76:B1:2D:...:B1:BE:E0:28:5E" /> </application>
注:
android:certDigest
に誤った値を入力すると、次のエラーが発生します。Installation failed due to: 'Failed to commit install session \ SESSION_ID with command cmd package install-commit SESSION_ID. \ Error: INSTALL_FAILED_MISSING_SHARED_LIBRARY: Reconciliation \ failed...: Reconcile failed: Package PACKAGE_NAME \ requires differently signed sdk library; failing!'
アプリをデプロイする
クライアント アプリを実行する前に、Android Studio またはコマンドラインを使用して、テストデバイスに SDK アプリとクライアント アプリをインストールします。
Android Studio を使用してデプロイする
Android Studio を使用してデプロイする手順は次のとおりです。
- SDK アプリの Android Studio プロジェクトを開きます。
- [Run] > [Edit Configurations] に移動します。[Run/Debug Configuration] ウィンドウが表示されます。
- 起動するアクティビティがないため、[Launch Options] で [Launch] を [Nothing] に設定します。
- [Apply]、[OK] の順にクリックします。
- 実行ボタン
をクリックして、テストデバイスに SDK アプリをインストールします。
- [Project] ツール ウィンドウで、クライアント アプリ モジュールに移動します。
- [Run] > [Edit Configurations] に移動します。[Run/Debug Configuration] ウィンドウが表示されます。
- [Launch Options] をクライアント アプリのメイン アクティビティに設定します。
- [Apply]、[OK] の順にクリックします。
- 実行ボタン
をクリックして、テストデバイスにクライアント アプリをインストールします。
コマンドラインでデプロイする
コマンドラインを使用してデプロイする場合の手順は次のとおりです。このセクションでは、SDK アプリ モジュールの名前が sdk-app
であり、クライアント アプリ モジュールの名前が client-app
であると仮定しています。
SDK アプリをデプロイします。
./gradlew sdk-app:installDebug
クライアント アプリをデプロイします。
./gradlew client-app:installDebug && \ # Start the app's activity. This example uses the sample app. adb shell am start -n \ com.example.privacysandbox.client/com.example.client.MainActivity
アプリをデバッグする
クライアント アプリをデバッグするには、Android Studio でデバッグボタン をクリックします。
SDK アプリをデバッグするには、[Run] > [Attach to Process] に移動します。ポップアップ画面が表示されます(図 1)。[Show all processes] チェックボックスをオンにします。表示されたリストの中から sdk_sandbox_CLIENT_APP_UID
というプロセスを探します。このオプションを選択し、SDK アプリのコードにブレークポイントを追加して、SDK のデバッグを開始します。
制限事項
SDK ランタイムに関する開発中の機能の一覧については、リリースノートをご覧ください。
コードサンプル
GitHub の SDK ランタイムとプライバシー保護 API のリポジトリには、SDK ランタイムの初期化方法と呼び出し方法を示すサンプルなど、使用を開始するために役立つ個別の Android Studio プロジェクトのセットが含まれています。
バグと問題を報告する
皆様からのフィードバックは、Android 版プライバシー サンドボックスに欠かせない要素です。問題が見つかった場合や Android 版プライバシー サンドボックスを改善するためのアイデアがありましたらお知らせください。