SDK ランタイム デベロッパー ガイド

フィードバックを送信

SDK ランタイムを使用すると、呼び出し元アプリとは別の専用のサンドボックスで SDK を実行でき、ユーザーデータの収集に関する安全性と確実性が向上します。SDK ランタイムは、実行環境を変更してデータアクセス権と許可された権限セットを制限することでこれを実現しています。SDK ランタイムの詳細については設計案をご覧ください。

このページでは、呼び出し元アプリにリモートでレンダリングできるウェブベースのビューを定義するランタイム対応の SDK を作成する手順について説明します。

始める前に

まず、次の手順を実施します。

  1. Android 版プライバシー サンドボックス用に開発環境をセットアップします。
  2. サポート対象のデバイスにシステム イメージをインストールするか、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 が返されます。次に例を示します。

ストレージには、指定の 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 を呼び出すには、呼び出し元のクライアント アプリを次のように変更します。

  1. 広告を含むアプリのアクティビティで、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;
  2. 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
    }
}
  1. 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);
        });
    }
    
  2. 証明書ダイジェストを手動で指定します。証明書ダイジェストを確認するには、keytool を使用してデバッグ キーストア ファイルから抽出します。デフォルトのパスワードは android です。

    keytool -list -keystore ~/.android/debug.keystore
    
  3. アプリのマニフェスト ファイルの <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 を使用してデプロイする手順は次のとおりです。

  1. SDK アプリの Android Studio プロジェクトを開きます。
  2. [Run] > [Edit Configurations] に移動します。[Run/Debug Configuration] ウィンドウが表示されます。
  3. 起動するアクティビティがないため、[Launch Options] で [Launch] を [Nothing] に設定します。
  4. [Apply]、[OK] の順にクリックします。
  5. 実行ボタン をクリックして、テストデバイスに SDK アプリをインストールします。
  6. [Project] ツール ウィンドウで、クライアント アプリ モジュールに移動します。
  7. [Run] > [Edit Configurations] に移動します。[Run/Debug Configuration] ウィンドウが表示されます。
  8. [Launch Options] をクライアント アプリのメイン アクティビティに設定します。
  9. [Apply]、[OK] の順にクリックします。
  10. 実行ボタン をクリックして、テストデバイスにクライアント アプリをインストールします。

コマンドラインでデプロイする

コマンドラインを使用してデプロイする場合の手順は次のとおりです。このセクションでは、SDK アプリ モジュールの名前が sdk-app であり、クライアント アプリ モジュールの名前が client-app であると仮定しています。

  1. SDK アプリをデプロイします。

    ./gradlew sdk-app:installDebug
    
  2. クライアント アプリをデプロイします。

    ./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 アプリのプロセスはダイアログ下部のリストビューに表示される
図 1. デバッグする SDK アプリを選択できる [Choose Process] 画面

制限事項

SDK ランタイムに関する開発中の機能の一覧については、リリースノートをご覧ください。

コードサンプル

GitHub の SDK ランタイムとプライバシー保護 API のリポジトリには、SDK ランタイムの初期化方法と呼び出し方法を示すサンプルなど、使用を開始するために役立つ個別の Android Studio プロジェクトのセットが含まれています。

バグと問題を報告する

皆様からのフィードバックは、Android 版プライバシー サンドボックスに欠かせない要素です。問題が見つかった場合や Android 版プライバシー サンドボックスを改善するためのアイデアがありましたらお知らせください。