/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ondevicepersonalization.services.display; import android.adservices.ondevicepersonalization.RenderOutputParcel; import android.adservices.ondevicepersonalization.RequestLogRecord; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.display.DisplayManager; import android.os.IBinder; import android.os.PersistableBundle; import android.view.Display; import android.view.SurfaceControlViewHost; import android.view.SurfaceControlViewHost.SurfacePackage; import android.view.WindowManager; import android.webkit.WebSettings; import android.webkit.WebView; import com.android.odp.module.common.PackageUtils; import com.android.ondevicepersonalization.internal.util.LoggerFactory; import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao; import com.android.ondevicepersonalization.services.display.velocity.VelocityEngineFactory; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import org.apache.velocity.Template; import org.apache.velocity.app.VelocityEngine; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; /** Helper class to display personalized content. */ public class DisplayHelper { private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); private static final String TAG = "DisplayHelper"; @NonNull private final Context mContext; public DisplayHelper(Context context) { mContext = context; } /** Generates an HTML string from the template data in RenderOutputParcel. */ @NonNull public String generateHtml( @NonNull RenderOutputParcel renderContentResult, @NonNull ComponentName service) { // If htmlContent is provided, do not render the template. String htmlContent = renderContentResult.getContent(); if (null != htmlContent && !htmlContent.isEmpty()) { return htmlContent; } PersistableBundle templateParams = renderContentResult.getTemplateParams(); String templateId = renderContentResult.getTemplateId(); if (null == templateParams || null == templateId) { throw new IllegalArgumentException( "Valid rendering output not provided for generateHtml"); } try { byte[] templateBytes = OnDevicePersonalizationVendorDataDao.getInstance( mContext, service, PackageUtils.getCertDigest(mContext, service.getPackageName())) .readSingleVendorDataRow(templateId); if (null == templateBytes) { throw new IllegalArgumentException( "Provided templateId not found during generateHtml"); } String templateContent = new String(templateBytes, StandardCharsets.UTF_8); // Move the template into a temp file to pass to Velocity. String templateFileName = createTempTemplateFile( templateContent, service.getPackageName()); VelocityEngine ve = VelocityEngineFactory.getVelocityEngine(mContext); Template template = ve.getTemplate(templateFileName); org.apache.velocity.context.Context ctx = VelocityEngineFactory.createVelocityContext(templateParams); StringWriter writer = new StringWriter(); template.merge(ctx, writer); return writer.toString(); } catch (PackageManager.NameNotFoundException | IOException e) { throw new RuntimeException(e); } } private String createTempTemplateFile(String templateContent, String templateId) throws IOException { File temp = File.createTempFile(templateId, ".vm", mContext.getCacheDir()); try (PrintWriter out = new PrintWriter(temp)) { out.print(templateContent); } temp.deleteOnExit(); return temp.getName(); } /** Creates a webview and displays the provided HTML. */ @NonNull public ListenableFuture displayHtml( @NonNull String html, @Nullable RequestLogRecord logRecord, long queryId, @NonNull ComponentName service, @NonNull IBinder hostToken, int displayId, int width, int height) { SettableFuture result = SettableFuture.create(); try { sLogger.d(TAG + ": displayHtml"); OnDevicePersonalizationExecutors.getHandlerForMainThread().post(() -> { createWebView(html, logRecord, queryId, service, hostToken, displayId, width, height, result); }); } catch (Exception e) { result.setException(e); } return result; } private void createWebView( @NonNull String html, @Nullable RequestLogRecord logRecord, long queryId, @NonNull ComponentName service, @NonNull IBinder hostToken, int displayId, int width, int height, @NonNull SettableFuture resultFuture) { try { sLogger.d(TAG + ": createWebView() started"); Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId); Context displayContext = mContext.createDisplayContext(display); Context windowContext = displayContext.createWindowContext( WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, null); WebView webView = new WebView(windowContext); webView.setWebViewClient( new OdpWebViewClient(mContext, service, queryId, logRecord)); WebSettings webViewSettings = webView.getSettings(); // Do not allow using file:// or content:// URLs. webViewSettings.setAllowFileAccess(false); webViewSettings.setAllowContentAccess(false); webView.loadData(html, "text/html; charset=utf-8", "UTF-8"); SurfaceControlViewHost host = new SurfaceControlViewHost( windowContext, display, hostToken); host.setView(webView, width, height); SurfacePackage surfacePackage = host.getSurfacePackage(); sLogger.d(TAG + ": createWebView success: " + surfacePackage); resultFuture.set(surfacePackage); } catch (Exception e) { sLogger.d(TAG + ": createWebView failed", e); resultFuture.setException(e); } } }