godot/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java

/*
 * Copyright (C) 2012 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.google.android.vending.expansion.downloader;

import com.google.android.vending.expansion.downloader.impl.DownloaderService;

import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;

// -- GODOT start --
import java.lang.ref.WeakReference;
// -- GODOT end --


/**
 * This class binds the service API to your application client.  It contains the IDownloaderClient proxy,
 * which is used to call functions in your client as well as the Stub, which is used to call functions
 * in the client implementation of IDownloaderClient.
 *
 * <p>The IPC is implemented using an Android Messenger and a service Binder.  The connect method
 * should be called whenever the client wants to bind to the service.  It opens up a service connection
 * that ends up calling the onServiceConnected client API that passes the service messenger
 * in.  If the client wants to be notified by the service, it is responsible for then passing its
 * messenger to the service in a separate call.
 *
 * <p>Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}.
 *
 * <p>When your application first starts, you should first check whether your app's expansion files are
 * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which
 * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method
 * returns a value indicating whether download is required or not.
 *
 * <p>If a download is required, {@link #startDownloadServiceIfRequired} begins the download through
 * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link
 * IStub} object that you need in order to receive calls through your {@link IDownloaderClient}
 * interface.
 */
public class DownloaderClientMarshaller {
    public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10;
    public static final int MSG_ONDOWNLOADPROGRESS = 11;
    public static final int MSG_ONSERVICECONNECTED = 12;

    public static final String PARAM_NEW_STATE = "newState";
    public static final String PARAM_PROGRESS = "progress";
    public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER;

    public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED;
    public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED;
    public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED;

    private static class Proxy implements IDownloaderClient {
        private Messenger mServiceMessenger;

        @Override
        public void onDownloadStateChanged(int newState) {
            Bundle params = new Bundle(1);
            params.putInt(PARAM_NEW_STATE, newState);
            send(MSG_ONDOWNLOADSTATE_CHANGED, params);
        }

        @Override
        public void onDownloadProgress(DownloadProgressInfo progress) {
            Bundle params = new Bundle(1);
            params.putParcelable(PARAM_PROGRESS, progress);
            send(MSG_ONDOWNLOADPROGRESS, params);
        }

        private void send(int method, Bundle params) {
            Message m = Message.obtain(null, method);
            m.setData(params);
            try {
                mServiceMessenger.send(m);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public Proxy(Messenger msg) {
            mServiceMessenger = msg;
        }

        @Override
        public void onServiceConnected(Messenger m) {
            /**
             * This is never called through the proxy.
             */
        }
    }

    private static class Stub implements IStub {
        private IDownloaderClient mItf = null;
        private Class<?> mDownloaderServiceClass;
        private boolean mBound;
        private Messenger mServiceMessenger;
        private Context mContext;
        /**
         * Target we publish for clients to send messages to IncomingHandler.
         */
        // -- GODOT start --
        private final MessengerHandlerClient mMsgHandler = new MessengerHandlerClient(this);
        final Messenger mMessenger = new Messenger(mMsgHandler);

        private static class MessengerHandlerClient extends Handler {
            private final WeakReference<Stub> mDownloader;
            public MessengerHandlerClient(Stub downloader) {
                mDownloader = new WeakReference<>(downloader);
            }

            @Override
            public void handleMessage(Message msg) {
                Stub downloader = mDownloader.get();
                if (downloader != null) {
                    downloader.handleMessage(msg);
                }
            }
        }

        private void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ONDOWNLOADPROGRESS:
                    Bundle bun = msg.getData();
                    if (null != mContext) {
                        bun.setClassLoader(mContext.getClassLoader());
                        DownloadProgressInfo dpi = (DownloadProgressInfo)msg.getData()
                                                            .getParcelable(PARAM_PROGRESS);
                        mItf.onDownloadProgress(dpi);
                    }
                    break;
                case MSG_ONDOWNLOADSTATE_CHANGED:
                    mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE));
                    break;
                case MSG_ONSERVICECONNECTED:
                    mItf.onServiceConnected(
                            (Messenger)msg.getData().getParcelable(PARAM_MESSENGER));
                    break;
            }
        }
        // -- GODOT end --

        public Stub(IDownloaderClient itf, Class<?> downloaderService) {
            mItf = itf;
            mDownloaderServiceClass = downloaderService;
        }

        /**
         * Class for interacting with the main interface of the service.
         */
        private ServiceConnection mConnection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                // This is called when the connection with the service has been
                // established, giving us the object we can use to
                // interact with the service. We are communicating with the
                // service using a Messenger, so here we get a client-side
                // representation of that from the raw IBinder object.
                mServiceMessenger = new Messenger(service);
                mItf.onServiceConnected(
                        mServiceMessenger);
            }

            public void onServiceDisconnected(ComponentName className) {
                // This is called when the connection with the service has been
                // unexpectedly disconnected -- that is, its process crashed.
                mServiceMessenger = null;
            }
        };

        @Override
        public void connect(Context c) {
            mContext = c;
            Intent bindIntent = new Intent(c, mDownloaderServiceClass);
            bindIntent.putExtra(PARAM_MESSENGER, mMessenger);
            if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) {
                if ( Constants.LOGVV ) {
                    Log.d(Constants.TAG, "Service Unbound");
                }
            } else {
                mBound = true;
            }

        }

        @Override
        public void disconnect(Context c) {
            if (mBound) {
                c.unbindService(mConnection);
                mBound = false;
            }
            mContext = null;
        }

        @Override
        public Messenger getMessenger() {
            return mMessenger;
        }
    }

    /**
     * Returns a proxy that will marshal calls to IDownloaderClient methods
     *
     * @param msg
     * @return
     */
    public static IDownloaderClient CreateProxy(Messenger msg) {
        return new Proxy(msg);
    }

    /**
     * Returns a stub object that, when connected, will listen for marshaled
     * {@link IDownloaderClient} methods and translate them into calls to the supplied
     * interface.
     *
     * @param itf An implementation of IDownloaderClient that will be called
     *            when remote method calls are unmarshaled.
     * @param downloaderService The class for your implementation of {@link
     * impl.DownloaderService}.
     * @return The {@link IStub} that allows you to connect to the service such that
     * your {@link IDownloaderClient} receives status updates.
     */
    public static IStub CreateStub(IDownloaderClient itf, Class<?> downloaderService) {
        return new Stub(itf, downloaderService);
    }

    /**
     * Starts the download if necessary. This function starts a flow that does `
     * many things. 1) Checks to see if the APK version has been checked and
     * the metadata database updated 2) If the APK version does not match,
     * checks the new LVL status to see if a new download is required 3) If the
     * APK version does match, then checks to see if the download(s) have been
     * completed 4) If the downloads have been completed, returns
     * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the
     * startup of an application to quickly ascertain if the application needs
     * to wait to hear about any updated APK expansion files. Note that this does
     * mean that the application MUST be run for the first time with a network
     * connection, even if Market delivers all of the files.
     *
     * @param context Your application Context.
     * @param notificationClient A PendingIntent to start the Activity in your application
     * that shows the download progress and which will also start the application when download
     * completes.
     * @param serviceClass the class of your {@link imp.DownloaderService} implementation
     * @return whether the service was started and the reason for starting the service.
     * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link
     * #DOWNLOAD_REQUIRED}.
     * @throws NameNotFoundException
     */
    public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient,
            Class<?> serviceClass)
            throws NameNotFoundException {
        return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
                serviceClass);
    }

    /**
     * This version assumes that the intent contains the pending intent as a parameter. This
     * is used for responding to alarms.
     * <p>The pending intent must be in an extra with the key {@link
     * impl.DownloaderService#EXTRA_PENDING_INTENT}.
     *
     * @param context
     * @param notificationClient
     * @param serviceClass the class of the service to start
     * @return
     * @throws NameNotFoundException
     */
    public static int startDownloadServiceIfRequired(Context context, Intent notificationClient,
            Class<?> serviceClass)
            throws NameNotFoundException {
        return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
                serviceClass);
    }

}