10d4fc55861ed4393aa82f124f2865695ef564641Marc Blank/*
20d4fc55861ed4393aa82f124f2865695ef564641Marc Blank /*
30d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * Copyright (C) 2011 The Android Open Source Project
40d4fc55861ed4393aa82f124f2865695ef564641Marc Blank *
50d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * Licensed under the Apache License, Version 2.0 (the "License");
60d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * you may not use this file except in compliance with the License.
70d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * You may obtain a copy of the License at
80d4fc55861ed4393aa82f124f2865695ef564641Marc Blank *
90d4fc55861ed4393aa82f124f2865695ef564641Marc Blank *      http://www.apache.org/licenses/LICENSE-2.0
100d4fc55861ed4393aa82f124f2865695ef564641Marc Blank *
110d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * Unless required by applicable law or agreed to in writing, software
120d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * distributed under the License is distributed on an "AS IS" BASIS,
130d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
140d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * See the License for the specific language governing permissions and
150d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * limitations under the License.
160d4fc55861ed4393aa82f124f2865695ef564641Marc Blank */
170d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
180d4fc55861ed4393aa82f124f2865695ef564641Marc Blankpackage com.android.emailcommon.service;
190d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
200d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport android.content.ComponentName;
210d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport android.content.Context;
220d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport android.content.Intent;
230d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport android.content.ServiceConnection;
240d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport android.os.Debug;
250d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport android.os.IBinder;
260d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport android.os.RemoteException;
270d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport android.util.Log;
280d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
290d4fc55861ed4393aa82f124f2865695ef564641Marc Blank/**
300d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * The EmailServiceProxy class provides a simple interface for the UI to call into the various
310d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * EmailService classes (e.g. ExchangeService for EAS).  It wraps the service connect/disconnect
320d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * process so that the caller need not be concerned with it.
330d4fc55861ed4393aa82f124f2865695ef564641Marc Blank *
340d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * Use the class like this:
350d4fc55861ed4393aa82f124f2865695ef564641Marc Blank *   new EmailServiceClass(context, class).loadAttachment(attachmentId, callback)
360d4fc55861ed4393aa82f124f2865695ef564641Marc Blank *
370d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * Methods without a return value return immediately (i.e. are asynchronous); methods with a
380d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * return value wait for a result from the Service (i.e. they should not be called from the UI
390d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * thread) with a default timeout of 30 seconds (settable)
400d4fc55861ed4393aa82f124f2865695ef564641Marc Blank *
410d4fc55861ed4393aa82f124f2865695ef564641Marc Blank * An EmailServiceProxy object cannot be reused (trying to do so generates a RemoteException)
420d4fc55861ed4393aa82f124f2865695ef564641Marc Blank */
430d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
440d4fc55861ed4393aa82f124f2865695ef564641Marc Blankpublic abstract class ServiceProxy {
45da3c4b8261825063ddf081e9335823569b813bd3Marc Blank    private static final boolean DEBUG_PROXY = false; // DO NOT CHECK THIS IN SET TO TRUE
46fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank    private final String mTag;
470d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
480d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private final Context mContext;
490d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    protected final Intent mIntent;
500d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private Runnable mRunnable = new ProxyRunnable();
510d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private ProxyTask mTask;
520d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private String mName = " unnamed";
530d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private final ServiceConnection mConnection = new ProxyConnection();
540d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    // Service call timeout (in seconds)
550d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private int mTimeout = 45;
56fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank    private long mStartTime;
570d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private boolean mDead = false;
580d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
590d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public abstract void onConnected(IBinder binder);
600d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
610d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public ServiceProxy(Context _context, Intent _intent) {
620d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        mContext = _context;
630d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        mIntent = _intent;
64fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank        mTag = getClass().getSimpleName();
650d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        if (Debug.isDebuggerConnected()) {
660d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            mTimeout <<= 2;
670d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
680d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
690d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
700d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private class ProxyConnection implements ServiceConnection {
710d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        public void onServiceConnected(ComponentName name, IBinder binder) {
720d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            onConnected(binder);
730d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            if (DEBUG_PROXY) {
74bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                Log.v(mTag, "Connected: " + name.getShortClassName());
750d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            }
760d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            // Run our task on a new thread
770d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            new Thread(new Runnable() {
780d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                public void run() {
798ba8c1648c38cd339b09501a057fc2f5b0658b10Marc Blank                    try {
808ba8c1648c38cd339b09501a057fc2f5b0658b10Marc Blank                        runTask();
818ba8c1648c38cd339b09501a057fc2f5b0658b10Marc Blank                    } finally {
828ba8c1648c38cd339b09501a057fc2f5b0658b10Marc Blank                        endTask();
838ba8c1648c38cd339b09501a057fc2f5b0658b10Marc Blank                    }
840d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                }}).start();
850d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
860d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
870d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        public void onServiceDisconnected(ComponentName name) {
880d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            if (DEBUG_PROXY) {
89bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                Log.v(mTag, "Disconnected: " + name.getShortClassName());
900d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            }
910d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
920d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
930d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
940d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public interface ProxyTask {
950d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        public void run() throws RemoteException;
960d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
970d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
980d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private class ProxyRunnable implements Runnable {
990d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        @Override
1000d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        public void run() {
1010d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            try {
1020d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                mTask.run();
1030d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            } catch (RemoteException e) {
1040d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            }
1050d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
1060d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
1070d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1080d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public ServiceProxy setTimeout(int secs) {
1090d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        mTimeout = secs;
1100d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        return this;
1110d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
1120d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1130d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public int getTimeout() {
1140d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        return mTimeout;
1150d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
1160d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1170d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public void endTask() {
1180d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        try {
1190d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            mContext.unbindService(mConnection);
1200d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        } catch (IllegalArgumentException e) {
1210d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            // This can happen if the user ended the activity that was using the service
1220d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            // This is harmless, but we've got to catch it
1230d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
1240d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1250d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        mDead = true;
1260d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        synchronized(mConnection) {
1270d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            if (DEBUG_PROXY) {
128fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank                Log.v(mTag, "Task " + mName + " completed; disconnecting");
1290d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            }
1300d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            mConnection.notify();
1310d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
1320d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
1330d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1340d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    private void runTask() {
1350d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        Thread thread = new Thread(mRunnable);
1360d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        thread.start();
1370d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        try {
1380d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            thread.join();
1390d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        } catch (InterruptedException e) {
1400d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
1410d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
1420d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1430d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public boolean setTask(ProxyTask task, String name) {
1440d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        mName = name;
1450d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        return setTask(task);
1460d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
1470d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1480d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public boolean setTask(ProxyTask task) throws IllegalStateException {
1490d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        if (mDead) {
1500d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            throw new IllegalStateException();
1510d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
1520d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        mTask = task;
153fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank        mStartTime = System.currentTimeMillis();
1540d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        if (DEBUG_PROXY) {
155fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank            Log.v(mTag, "Bind requested for task " + mName);
1560d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
1570d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        return mContext.bindService(mIntent, mConnection, Context.BIND_AUTO_CREATE);
1580d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
1590d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1600d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public void waitForCompletion() {
1610d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        synchronized (mConnection) {
1620d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            long time = System.currentTimeMillis();
1630d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            try {
1640d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                if (DEBUG_PROXY) {
165fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank                    Log.v(mTag, "Waiting for task " + mName + " to complete...");
1660d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                }
1670d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                mConnection.wait(mTimeout * 1000L);
1680d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            } catch (InterruptedException e) {
1690d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                // Can be ignored safely
1700d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            }
1710d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            if (DEBUG_PROXY) {
172bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                Log.v(mTag, "Wait for " + mName + " finished in " +
173fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank                        (System.currentTimeMillis() - time) + "ms");
1740d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            }
1750d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
1760d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
1770d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1780d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public void close() throws RemoteException {
1790d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        if (mDead) {
1800d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            throw new RemoteException();
1810d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
1820d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        endTask();
1830d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
1840d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
1850d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    /**
1860d4fc55861ed4393aa82f124f2865695ef564641Marc Blank     * Connection test; return indicates whether the remote service can be connected to
1870d4fc55861ed4393aa82f124f2865695ef564641Marc Blank     * @return the result of trying to connect to the remote service
1880d4fc55861ed4393aa82f124f2865695ef564641Marc Blank     */
1890d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    public boolean test() {
1900d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        try {
1910d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            return setTask(new ProxyTask() {
1920d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                public void run() throws RemoteException {
1930d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                    if (DEBUG_PROXY) {
194fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank                        Log.v(mTag, "Connection test succeeded in " +
195fdec974c93817988a00248b7d223ec9f7f20ecb8Marc Blank                                (System.currentTimeMillis() - mStartTime) + "ms");
1960d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                    }
1970d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                }
1980d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            }, "test");
1990d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        } catch (Exception e) {
2000d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            // For any failure, return false.
2010d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            return false;
2020d4fc55861ed4393aa82f124f2865695ef564641Marc Blank        }
2030d4fc55861ed4393aa82f124f2865695ef564641Marc Blank    }
2040d4fc55861ed4393aa82f124f2865695ef564641Marc Blank}
205