/* * Copyright © 2016 TIBCO Software,Inc.All rights reserved. * http://community.jaspersoft.com/project/jaspermobile-android * * Unless you have purchased a commercial license agreement from TIBCO Jaspersoft, * the following license terms apply: * * This program is part of TIBCO Jaspersoft Mobile for Android. * * TIBCO Jaspersoft Mobile is free software:you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation,either version 3of the License,or * (at your option)any later version. * * TIBCO Jaspersoft Mobile is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY;without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with TIBCO Jaspersoft Mobile for Android.If not,see * <http://www.gnu.org/licenses/lgpl>. */ package com.jaspersoft.android.jaspermobile.util.cast; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.v7.app.NotificationCompat; import android.view.Display; import android.view.View; import android.view.WindowManager; import android.widget.ProgressBar; import com.google.android.gms.cast.CastPresentation; import com.google.android.gms.cast.CastRemoteDisplayLocalService; import com.jaspersoft.android.jaspermobile.Analytics; import com.jaspersoft.android.jaspermobile.R; import com.jaspersoft.android.jaspermobile.activities.viewer.html.report.ReportCastActivity_; import com.jaspersoft.android.jaspermobile.domain.AppCredentials; import com.jaspersoft.android.jaspermobile.domain.JasperServer; import com.jaspersoft.android.jaspermobile.domain.SimpleSubscriber; import com.jaspersoft.android.jaspermobile.domain.VisualizeTemplate; import com.jaspersoft.android.jaspermobile.domain.interactor.report.GetVisualizeExecOptionsCase; import com.jaspersoft.android.jaspermobile.domain.interactor.report.GetVisualizeTemplateCase; import com.jaspersoft.android.jaspermobile.network.RequestExceptionHandler; import com.jaspersoft.android.jaspermobile.ui.model.visualize.VisualizeExecOptions; import com.jaspersoft.android.jaspermobile.ui.view.activity.NavigationActivity_; import com.jaspersoft.android.jaspermobile.ui.view.fragment.ComponentProviderDelegate; import com.jaspersoft.android.jaspermobile.util.ReportParamsStorage; import com.jaspersoft.android.jaspermobile.util.ScreenUtil_; import com.jaspersoft.android.jaspermobile.webview.DefaultSessionListener; import com.jaspersoft.android.jaspermobile.webview.DefaultUrlPolicy; import com.jaspersoft.android.jaspermobile.webview.ErrorWebViewClientListener; import com.jaspersoft.android.jaspermobile.webview.JasperChromeClientListenerImpl; import com.jaspersoft.android.jaspermobile.webview.JasperWebViewClientListener; import com.jaspersoft.android.jaspermobile.webview.SystemChromeClient; import com.jaspersoft.android.jaspermobile.webview.SystemWebViewClient; import com.jaspersoft.android.jaspermobile.webview.TimeoutWebViewClientListener; import com.jaspersoft.android.jaspermobile.webview.UrlPolicy; import com.jaspersoft.android.jaspermobile.webview.WebInterface; import com.jaspersoft.android.jaspermobile.webview.WebViewEnvironment; import com.jaspersoft.android.jaspermobile.webview.dashboard.InjectionRequestInterceptor; import com.jaspersoft.android.jaspermobile.webview.intercept.VisualizeResourcesInterceptRule; import com.jaspersoft.android.jaspermobile.webview.intercept.WebResourceInterceptor; import com.jaspersoft.android.jaspermobile.webview.intercept.okhttp.OkHttpWebResourceInterceptor; import com.jaspersoft.android.jaspermobile.webview.report.bridge.ReportCallback; import com.jaspersoft.android.jaspermobile.webview.report.bridge.ReportWebInterface; import com.jaspersoft.android.jaspermobile.widget.ScrollComputableWebView; import com.jaspersoft.android.sdk.client.oxm.resource.ResourceLookup; import com.squareup.okhttp.OkHttpClient; import java.util.ArrayList; import java.util.Collections; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import timber.log.Timber; /** * @author Andrew Tivodar * @since 2.3 */ public class ResourcePresentationService extends CastRemoteDisplayLocalService { public final static int IDLE = 0; public final static int INITIALIZED = 1; public final static int LOADING = 2; public final static int PRESENTING = 3; @Inject protected ReportParamsStorage paramsStorage; @Inject protected Analytics analytics; @Inject protected GetVisualizeTemplateCase mGetVisualizeTemplateCase; @Inject protected GetVisualizeExecOptionsCase mGetVisualizeExecOptionsCase; @Inject protected JasperServer mServer; @Inject @Named("webview_client") protected OkHttpClient webViewResourceClient; private ReportPresentation mPresentation; private String mCastDeviceName; private ArrayList<ResourcePresentationCallback> mReportPresentationListeners; private ResourceLookup mCurrentResource; private int mState; @Override public void onCreate() { super.onCreate(); ComponentProviderDelegate.INSTANCE .getProfileComponent(this) .inject(this); mReportPresentationListeners = new ArrayList<>(); } @Override public void onCreatePresentation(Display display) { onDismissPresentation(); mPresentation = new ReportPresentation(this, display); try { mPresentation.show(); } catch (WindowManager.InvalidDisplayException ex) { onDismissPresentation(); } analytics.sendEvent(Analytics.EventCategory.CAST.getValue(), Analytics.EventAction.PRESENTED.getValue(), null); } @Override public void onDismissPresentation() { if (mGetVisualizeTemplateCase != null) { mGetVisualizeTemplateCase.unsubscribe(); } if (mGetVisualizeExecOptionsCase != null) { mGetVisualizeExecOptionsCase.unsubscribe(); } if (mPresentation != null) { clearReportParams(); resetPresentation(); mPresentation.dismiss(); mPresentation = null; } changeState(IDLE); } public void setCastDeviceName(String castDeviceName) { this.mCastDeviceName = castDeviceName; } public static boolean isStarted() { return getInstance() != null; } public boolean isPresenting() { return mState == PRESENTING; } private void updateCastNotification() { NotificationSettings notificationSettings = new NotificationSettings.Builder() .setNotification(createCastNotification()) .build(); updateNotificationSettings(notificationSettings); } public void synchronizeState(ResourceLookup resourceLookup, ResourcePresentationCallback resourcePresentationCallback) { if (mCurrentResource != null && !mCurrentResource.getUri().equals(resourceLookup.getUri())) { clearReportParams(); resetPresentation(); } fetchState(resourcePresentationCallback); } public void fetchState(ResourcePresentationCallback resourcePresentationCallback) { switch (mState) { case IDLE: if (resourcePresentationCallback != null) { resourcePresentationCallback.onCastStarted(); } break; case INITIALIZED: if (resourcePresentationCallback != null) { resourcePresentationCallback.onInitializationDone(); } break; case LOADING: if (resourcePresentationCallback != null) { resourcePresentationCallback.onLoadingStarted(); } break; case PRESENTING: if (resourcePresentationCallback != null) { resourcePresentationCallback.onPresentationBegun(); if (mPresentation.getPageCount() == -1) { resourcePresentationCallback.onMultiPage(); } else { resourcePresentationCallback.onPageCountObtain(mPresentation.getPageCount()); } resourcePresentationCallback.onPageChanged(mPresentation.getCurrentPage(), null); } break; } } public synchronized void addResourcePresentationCallback(ResourcePresentationCallback resourcePresentationCallback) { this.mReportPresentationListeners.add(resourcePresentationCallback); } public synchronized void removeResourcePresentationCallback(ResourcePresentationCallback resourcePresentationCallback) { this.mReportPresentationListeners.remove(resourcePresentationCallback); } public void startPresentation(ResourceLookup resourceLookup) { if (resourceLookup.equals(mCurrentResource)) return; mCurrentResource = resourceLookup; mPresentation.castReport(resourceLookup.getUri()); analytics.sendEvent(Analytics.EventCategory.RESOURCE.getValue(), Analytics.EventAction.PRESENTED.getValue(), resourceLookup.getResourceType().name()); } public void closeCurrentPresentation() { clearReportParams(); resetPresentation(); for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onCastStopped(); } analytics.sendEvent(Analytics.EventCategory.RESOURCE.getValue(), Analytics.EventAction.PRESENTATION_STOPPED.getValue(), null); } public void applyParams(String params) { resetPresentation(); mPresentation.applyParams(params); } public void refresh() { resetPresentation(); mPresentation.refresh(); } public String getCurrentResourceLabel() { if (mCurrentResource == null) return null; return mCurrentResource.getLabel(); } public Bitmap getCurrentResourceThumbnail() { if (mCurrentResource == null || mState != PRESENTING) return null; return mPresentation.getThumbnail(); } public void selectPage(int pageNumber) { mPresentation.selectPage(pageNumber); } public void scrollUp() { mPresentation.scrollVertical(-8); } public void scrollDown() { mPresentation.scrollVertical(8); } public void scrollLeft() { mPresentation.scrollHorizontal(-8); } public void scrollRight() { mPresentation.scrollHorizontal(8); } public void zoomIn() { mPresentation.zoomIn(); } public void zoomOut() { mPresentation.zoomOut(); } //--------------------------------------------------------------------- // Helper methods //--------------------------------------------------------------------- private void changeState(int state) { mState = state; } private void clearReportParams() { if (mCurrentResource != null) { paramsStorage.clearInputControlHolder(mCurrentResource.getUri()); } } private void resetPresentation() { hidePresentationView(); changeState(INITIALIZED); updateCastNotification(); } private void hidePresentationView() { mPresentation.hideLoading(); mPresentation.hideReport(); } private void handleError(String error) { resetPresentation(); updateCastNotification(); for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onErrorOccurred(error); } } private Notification createCastNotification() { NotificationCompat.Builder castNotificationBuilder = new NotificationCompat.Builder(this); Intent intent = NavigationActivity_.intent(this).get(); String title; switch (mState) { case IDLE: title = getString(R.string.r_pd_initializing_msg); break; case INITIALIZED: title = getString(R.string.cast_ready_message); break; case LOADING: title = getString(R.string.r_pd_running_report_msg); intent = ReportCastActivity_.intent(this).resource(mCurrentResource).get(); break; case PRESENTING: title = getCurrentResourceLabel(); intent = ReportCastActivity_.intent(this).resource(mCurrentResource).get(); castNotificationBuilder .addAction(R.drawable.ic_menu_stop, "", PendingIntent.getBroadcast(this, 0, new Intent(getString(R.string.resource_cast_cancel_intent)), 0)) .setLargeIcon(getCurrentResourceThumbnail()) .setStyle(new NotificationCompat.MediaStyle() .setShowActionsInCompactView(0)); break; default: title = getString(R.string.r_pd_initializing_msg); } intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); castNotificationBuilder.setSmallIcon(R.drawable.im_logo_single) .setWhen(0) .setContentTitle(title) .setContentText(mCastDeviceName) .setContentIntent(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); Intent stopIntent = new Intent(getString(R.string.resource_presentation_stop_intent)); PendingIntent broadcast = PendingIntent.getBroadcast(this, 0, stopIntent, 0); castNotificationBuilder.addAction(R.drawable.ic_menu_close, "", broadcast); return castNotificationBuilder.build(); } public void reload() { startPresentation(mCurrentResource); } //--------------------------------------------------------------------- // Nested classes //--------------------------------------------------------------------- public interface ResourcePresentationCallback { void onCastStarted(); void onInitializationDone(); void onLoadingStarted(); void onPresentationBegun(); void onMultiPage(); void onPageCountObtain(int pageCount); void onPageChanged(int pageNumb, String errorMessage); void onErrorOccurred(String errorMessage); void onAuthErrorOccurred(); void onCastStopped(); } private class ReportPresentation extends CastPresentation implements ErrorWebViewClientListener.OnWebViewErrorListener, ReportCallback { private ScrollComputableWebView webView; private ProgressBar progressState; private int mPageCount; private int mCurrentPage; public ReportPresentation(Context serviceContext, Display display) { super(serviceContext, display); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.report_presentation); webView = (ScrollComputableWebView) findViewById(R.id.reportCastWebView); progressState = (ProgressBar) findViewById(R.id.progressLoading); prepareReportCasting(); } @Override public void onWebViewError(String title, String message, String failingUrl, int errorCode) { handleError(title + "\n" + message); } //--------------------------------------------------------------------- // Report presentation commands //--------------------------------------------------------------------- private void castReport(String reportUri) { mGetVisualizeExecOptionsCase.execute(reportUri, new SimpleSubscriber<VisualizeExecOptions.Builder>() { @Override public void onError(Throwable e) { Timber.e(e, "GetVisualizeExecOptionsCase failed"); String message = RequestExceptionHandler.extractMessage(ResourcePresentationService.this, e); handleError(message); } @Override public void onNext(VisualizeExecOptions.Builder item) { double diagonal = ScreenUtil_.getInstance_(getContext()).getDiagonal(); VisualizeExecOptions options = item.setDiagonal(diagonal).build(); StringBuilder builder = new StringBuilder(); builder.append("javascript:MobileReport.configure") .append("({ \"auth\": ") .append("{") .append("\"username\": \"%s\",") .append("\"password\": \"%s\",") .append("\"organization\": \"%s\"") .append("}, ") .append("\"diagonal\": %s ") .append("})") .append(".run({") .append("\"uri\": \"%s\",") .append("\"params\": %s") .append("})"); AppCredentials credentials = options.getAppCredentials(); final String executeScript = String.format(builder.toString(), credentials.getUsername(), credentials.getPassword(), credentials.getOrganization(), ScreenUtil_.getInstance_(getContext()).getDiagonal(), options.getUri(), options.getParams() ); webView.loadUrl(executeScript); } }); } private void applyParams(String params) { webView.loadUrl(String.format("javascript:MobileReport.applyReportParams(%s)", params)); } private void refresh() { webView.loadUrl("javascript:MobileReport.refresh()"); } public Bitmap getThumbnail() { Bitmap thumbnail = Bitmap.createBitmap(webView.getWidth(), webView.getHeight(), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(thumbnail); webView.draw(canvas); return thumbnail; } public void selectPage(int pageNumber) { resetZoom(); webView.loadUrl(String.format("javascript:MobileReport.selectPage(%d)", pageNumber)); } private void scrollVertical(int scrollValue) { if (webView.canScrollVertically(scrollValue)) { webView.scrollBy(0, scrollValue); } } private void scrollHorizontal(int scrollValue) { if (webView.canScrollHorizontally(scrollValue)) { webView.scrollBy(scrollValue, 0); } } private void zoomIn() { webView.zoomIn(); } private void zoomOut() { webView.zoomOut(); } private void showReport() { webView.setVisibility(View.VISIBLE); } private void hideReport() { webView.setVisibility(View.INVISIBLE); while (webView.zoomOut()); } private void showLoading() { progressState.setVisibility(View.VISIBLE); } private void hideLoading() { progressState.setVisibility(View.GONE); } private int getPageCount() { return mPageCount; } private int getCurrentPage() { return mCurrentPage; } //--------------------------------------------------------------------- // Helper methods //--------------------------------------------------------------------- private void prepareReportCasting() { mPresentation.showLoading(); initWebView(); loadVisualize(); } private void initWebView() { JasperChromeClientListenerImpl chromeClientListener = new JasperChromeClientListenerImpl(new ProgressBar(getContext())); DefaultUrlPolicy.SessionListener sessionListener = DefaultSessionListener.from(null); UrlPolicy defaultPolicy = new DefaultUrlPolicy(mServer.getBaseUrl()) .withSessionListener(sessionListener); SystemChromeClient systemChromeClient = new SystemChromeClient.Builder(getContext()) .withDelegateListener(chromeClientListener) .build(); JasperWebViewClientListener errorListener = new ErrorWebViewClientListener(getContext(), this); JasperWebViewClientListener clientListener = TimeoutWebViewClientListener.wrap(errorListener); WebResourceInterceptor.Rule reportResourcesRule = VisualizeResourcesInterceptRule.getInstance(); WebResourceInterceptor cacheResourceInterceptor = new OkHttpWebResourceInterceptor.Builder() .withClient(webViewResourceClient) .registerRule(reportResourcesRule) .build(); WebResourceInterceptor injectionRequestInterceptor = InjectionRequestInterceptor.getInstance(); SystemWebViewClient systemWebViewClient = new SystemWebViewClient.Builder() .registerInterceptor(injectionRequestInterceptor) .registerInterceptor(cacheResourceInterceptor) .withDelegateListener(clientListener) .registerUrlPolicy(defaultPolicy) .build(); WebInterface mWebInterface = ReportWebInterface.from(this); WebViewEnvironment.configure(webView) .withDefaultSettings() .withChromeClient(systemChromeClient) .withWebClient(systemWebViewClient) .withWebInterface(mWebInterface); } private void loadVisualize() { double width = ScreenUtil_.getInstance_(getContext()).getWidth() * 1.5; Map<String, Double> clientParams = Collections.singletonMap("width", width); mGetVisualizeTemplateCase.execute(clientParams, new SimpleSubscriber<VisualizeTemplate>() { @Override public void onError(Throwable e) { Timber.e(e, "GetVisualizeTemplateCase failed"); String message = RequestExceptionHandler.extractMessage(ResourcePresentationService.this, e); handleError(message); } @Override public void onNext(VisualizeTemplate template) { webView.loadDataWithBaseURL(template.getServerUrl(), template.getContent(), "text/html", "utf-8", null); } }); } protected void resetZoom() { while (webView.zoomOut()) ; } //--------------------------------------------------------------------- // JS Report callback //--------------------------------------------------------------------- @Override public void onScriptLoaded() { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { hideLoading(); changeState(INITIALIZED); updateCastNotification(); for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onInitializationDone(); } } }); } @Override public void onLoadStart() { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { mPresentation.showLoading(); changeState(LOADING); updateCastNotification(); mCurrentPage = 1; mPageCount = -1; for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onLoadingStarted(); } } }); } @Override public void onLoadDone(String parameters) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { if (mState == INITIALIZED) return; mPresentation.hideLoading(); if (mPageCount != 0) { mPresentation.showReport(); } else { mPresentation.hideReport(); } changeState(PRESENTING); updateCastNotification(); for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onPresentationBegun(); } } }); } @Override public void onLoadError(final String error) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { handleError(error); } }); } @Override public void onReportCompleted(String status, final int pages, String errorMessage) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { mPageCount = pages; if (pages == 0) { hideReport(); } for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onPageCountObtain(pages); } } }); } @Override public void onPageChange(final int page) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onPageChanged(page, null); } mCurrentPage = page; } }); } @Override public void onReferenceClick(String location) { } @Override public void onReportExecutionClick(String data) { } @Override public void onMultiPageStateObtained(final boolean isMultiPage) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { if (!isMultiPage) return; for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onMultiPage(); } } }); } @Override public void onWindowError(String errorMessage) { } @Override public void onPageLoadError(final String errorMessage, final int page) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onPageChanged(page, errorMessage); } mCurrentPage = page; } }); } @Override public void onAuthError(final String error) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { resetPresentation(); for (ResourcePresentationCallback reportPresentationListener : mReportPresentationListeners) { reportPresentationListener.onAuthErrorOccurred(); } } }); } } }