249 lines
9.9 KiB
Java
249 lines
9.9 KiB
Java
/*
|
|
/*
|
|
* Copyright (C) 2011 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.emailcommon.service;
|
|
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.content.pm.ProviderInfo;
|
|
import android.os.AsyncTask;
|
|
import android.os.Debug;
|
|
import android.os.IBinder;
|
|
import android.os.Looper;
|
|
import android.os.RemoteException;
|
|
|
|
import com.android.emailcommon.provider.EmailContent;
|
|
import com.android.mail.utils.LogUtils;
|
|
|
|
import java.util.concurrent.Executor;
|
|
|
|
/**
|
|
* ServiceProxy is a superclass for proxy objects which make a single call to a service. It handles
|
|
* connecting to the service, running a task supplied by the subclass when the connection is ready,
|
|
* and disconnecting from the service afterwards. ServiceProxy objects cannot be reused (trying to
|
|
* do so generates an {@link IllegalStateException}).
|
|
*
|
|
* Subclasses must override {@link #onConnected} to store the binder. Then, when the subclass wants
|
|
* to make a service call, it should call {@link #setTask}, supplying the {@link ProxyTask} that
|
|
* should run when the connection is ready. {@link ProxyTask#run} should implement the necessary
|
|
* logic to make the call on the service.
|
|
*/
|
|
|
|
public abstract class ServiceProxy {
|
|
public static final String EXTRA_FORCE_SHUTDOWN = "ServiceProxy.FORCE_SHUTDOWN";
|
|
|
|
private static final boolean DEBUG_PROXY = false; // DO NOT CHECK THIS IN SET TO TRUE
|
|
private final String mTag;
|
|
|
|
private final Context mContext;
|
|
protected final Intent mIntent;
|
|
private ProxyTask mTask;
|
|
private String mName = " unnamed";
|
|
private final ServiceConnection mConnection = new ProxyConnection();
|
|
// Service call timeout (in seconds)
|
|
private int mTimeout = 45;
|
|
private long mStartTime;
|
|
private boolean mTaskSet = false;
|
|
private boolean mTaskCompleted = false;
|
|
|
|
public static Intent getIntentForEmailPackage(Context context, String actionName) {
|
|
/**
|
|
* We want to scope the intent so that only the Email app will handle it. Unfortunately
|
|
* we found that there are many instances where the package name of the Email app is
|
|
* not what we expect. The easiest way to find the package of the correct app is to
|
|
* see who is the EmailContent.AUTHORITY as there is only one app that can implement
|
|
* the content provider for this authority and this is the right app to handle this intent.
|
|
*/
|
|
final Intent intent = new Intent(EmailContent.EMAIL_PACKAGE_NAME + "." + actionName);
|
|
final ProviderInfo info = context.getPackageManager().resolveContentProvider(
|
|
EmailContent.AUTHORITY, 0);
|
|
if (info != null) {
|
|
final String packageName = info.packageName;
|
|
intent.setPackage(packageName);
|
|
} else {
|
|
LogUtils.e(LogUtils.TAG, "Could not find the Email Content Provider");
|
|
}
|
|
return intent;
|
|
}
|
|
|
|
/**
|
|
* This function is called after the proxy connects to the service but before it runs its task.
|
|
* Subclasses must override this to store the binder correctly.
|
|
* @param binder The service IBinder.
|
|
*/
|
|
public abstract void onConnected(IBinder binder);
|
|
|
|
public ServiceProxy(Context _context, Intent _intent) {
|
|
mContext = _context;
|
|
mIntent = _intent;
|
|
mTag = getClass().getSimpleName();
|
|
if (Debug.isDebuggerConnected()) {
|
|
mTimeout <<= 2;
|
|
}
|
|
}
|
|
|
|
private class ProxyConnection implements ServiceConnection {
|
|
@Override
|
|
public void onServiceConnected(ComponentName name, IBinder binder) {
|
|
if (DEBUG_PROXY) {
|
|
LogUtils.v(mTag, "Connected: " + name.getShortClassName() + " at " +
|
|
(System.currentTimeMillis() - mStartTime) + "ms");
|
|
}
|
|
|
|
// Let subclasses handle the binder.
|
|
onConnected(binder);
|
|
|
|
// Do our work in another thread.
|
|
new AsyncTask<Void, Void, Void>() {
|
|
@Override
|
|
protected Void doInBackground(Void... params) {
|
|
try {
|
|
mTask.run();
|
|
} catch (RemoteException e) {
|
|
LogUtils.e(mTag, e, "RemoteException thrown running mTask!");
|
|
} finally {
|
|
// Make sure that we unbind the mConnection even on exceptions in the
|
|
// task provided by the subclass.
|
|
try {
|
|
// Each ServiceProxy handles just one task, so we unbind after we're
|
|
// done with our work.
|
|
mContext.unbindService(mConnection);
|
|
} catch (RuntimeException e) {
|
|
// The exceptions that are thrown here look like IllegalStateException,
|
|
// IllegalArgumentException and RuntimeException. Catching
|
|
// RuntimeException which get them all. Reasons for these exceptions
|
|
// include services that have already been stopped or unbound. This can
|
|
// happen if the user ended the activity that was using the service.
|
|
// This is harmless, but we've got to catch it.
|
|
LogUtils.e(mTag, e,
|
|
"RuntimeException when trying to unbind from service");
|
|
}
|
|
}
|
|
synchronized(mConnection) {
|
|
mTaskCompleted = true;
|
|
if (DEBUG_PROXY) {
|
|
LogUtils.v(mTag, "Task " + mName + " completed; disconnecting");
|
|
}
|
|
mConnection.notify();
|
|
}
|
|
return null;
|
|
}
|
|
}.executeOnExecutor(mTask.runInExecutor());
|
|
}
|
|
|
|
@Override
|
|
public void onServiceDisconnected(ComponentName name) {
|
|
if (DEBUG_PROXY) {
|
|
LogUtils.v(mTag, "Disconnected: " + name.getShortClassName() + " at " +
|
|
(System.currentTimeMillis() - mStartTime) + "ms");
|
|
}
|
|
}
|
|
}
|
|
|
|
protected abstract class ProxyTask {
|
|
public Executor runInExecutor() {
|
|
return AsyncTask.SERIAL_EXECUTOR;
|
|
};
|
|
public abstract void run() throws RemoteException;
|
|
}
|
|
|
|
public ServiceProxy setTimeout(int secs) {
|
|
mTimeout = secs;
|
|
return this;
|
|
}
|
|
|
|
public int getTimeout() {
|
|
return mTimeout;
|
|
}
|
|
|
|
protected boolean setTask(ProxyTask task, String name) throws IllegalStateException {
|
|
if (mTaskSet) {
|
|
throw new IllegalStateException("Cannot call setTask twice on the same ServiceProxy.");
|
|
}
|
|
mTaskSet = true;
|
|
mName = name;
|
|
mTask = task;
|
|
mStartTime = System.currentTimeMillis();
|
|
if (DEBUG_PROXY) {
|
|
LogUtils.v(mTag, "Bind requested for task " + mName);
|
|
}
|
|
synchronized (mConnection) {
|
|
mTaskCompleted = false;
|
|
}
|
|
return mContext.bindService(mIntent, mConnection, Context.BIND_AUTO_CREATE);
|
|
}
|
|
|
|
/**
|
|
* Callers that want to wait on the {@link ProxyTask} should call this immediately after calling
|
|
* {@link #setTask}. This will wait until the task completes, up to the timeout (which can be
|
|
* set with {@link #setTimeout}).
|
|
*/
|
|
protected void waitForCompletion() {
|
|
/*
|
|
* onServiceConnected() is always called on the main thread, and we block the current thread
|
|
* for up to 10 seconds as a timeout. If we're currently on the main thread,
|
|
* onServiceConnected() is not called until our timeout elapses (and the UI is frozen for
|
|
* the duration).
|
|
*/
|
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
throw new IllegalStateException("This cannot be called on the main thread.");
|
|
}
|
|
|
|
synchronized (mConnection) {
|
|
long time = System.currentTimeMillis();
|
|
try {
|
|
if (DEBUG_PROXY) {
|
|
LogUtils.v(mTag, "Waiting for task " + mName + " to complete...");
|
|
}
|
|
if (!mTaskCompleted) {
|
|
mConnection.wait(mTimeout * 1000L);
|
|
}
|
|
} catch (InterruptedException e) {
|
|
// Can be ignored safely
|
|
}
|
|
if (DEBUG_PROXY) {
|
|
LogUtils.v(mTag, "Wait for " + mName +
|
|
(mTaskCompleted ? " finished in " : " timed out in ") +
|
|
(System.currentTimeMillis() - time) + "ms");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Connection test; return indicates whether the remote service can be connected to
|
|
* @return the result of trying to connect to the remote service
|
|
*/
|
|
public boolean test() {
|
|
try {
|
|
return setTask(new ProxyTask() {
|
|
@Override
|
|
public void run() throws RemoteException {
|
|
if (DEBUG_PROXY) {
|
|
LogUtils.v(mTag, "Connection test succeeded in " +
|
|
(System.currentTimeMillis() - mStartTime) + "ms");
|
|
}
|
|
}
|
|
}, "test");
|
|
} catch (Exception e) {
|
|
// For any failure, return false.
|
|
return false;
|
|
}
|
|
}
|
|
}
|