diff options
Diffstat (limited to 'translate/TranslatorManager.java')
-rw-r--r-- | translate/TranslatorManager.java | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/translate/TranslatorManager.java b/translate/TranslatorManager.java new file mode 100644 index 0000000..841a041 --- /dev/null +++ b/translate/TranslatorManager.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2012 Google Inc. + * + * 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.googlecode.eyesfree.braille.translate; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +/** + * Client-side interface to the central braille translator service. + * + * This class can be used to retrieve {@link BrailleTranslator} instances for + * performing translation between text and braille cells. + * + * Typically, an instance of this class is created at application + * initialization time and destroyed using the {@link destroy()} method when + * the application is about to be destroyed. It is recommended that the + * instance is destroyed and recreated if braille translation is not going to + * be need for a long period of time. + * + * Threading:<br> + * The object must be destroyed on the same thread it was created. + * Other methods may be called from any thread. + */ +public class TranslatorManager { + private static final String LOG_TAG = + TranslatorManager.class.getSimpleName(); + private static final String ACTION_TRANSLATOR_SERVICE = + "com.googlecode.eyesfree.braille.service.ACTION_TRANSLATOR_SERVICE"; + private static final Intent mServiceIntent = + new Intent(ACTION_TRANSLATOR_SERVICE); + /** + * Delay before the first rebind attempt on bind error or service + * disconnect. + */ + private static final int REBIND_DELAY_MILLIS = 500; + private static final int MAX_REBIND_ATTEMPTS = 5; + public static final int ERROR = -1; + public static final int SUCCESS = 0; + + /** + * A callback interface to get notified when the translation + * manager is ready to be used, or an error occurred during + * initialization. + */ + public interface OnInitListener { + /** + * Called exactly once when it has been determined that the + * translation service is either ready to be used ({@code SUCCESS}) + * or the service is not available {@code ERROR}. + */ + public void onInit(int status); + } + + private final Context mContext; + private final TranslatorManagerHandler mHandler = + new TranslatorManagerHandler(); + private final ServiceCallback mServiceCallback = new ServiceCallback(); + + private OnInitListener mOnInitListener; + private Connection mConnection; + private int mNumFailedBinds = 0; + + /** + * Constructs an instance. {@code context} is used to bind to the + * translator service. The other methods of this class should not be + * called (they will fail) until {@code onInitListener.onInit()} + * is called. + */ + public TranslatorManager(Context context, OnInitListener onInitListener) { + mContext = context; + mOnInitListener = onInitListener; + doBindService(); + } + + /** + * Destroys this instance, deallocating any global resources it is using. + * Any {@link BrailleTranslator} objects that were created using this + * object are invalid after this call. + */ + public void destroy() { + doUnbindService(); + mHandler.destroy(); + } + + /** + * Returns a new {@link BrailleTranslator} for the translation + * table specified by {@code tableName}. + */ + // TODO: Document how to discover valid table names. + public BrailleTranslator getTranslator(String tableName) { + ITranslatorService localService = getTranslatorService(); + if (localService != null) { + try { + if (localService.checkTable(tableName)) { + return new BrailleTranslatorImpl(tableName); + } + } catch (RemoteException ex) { + Log.e(LOG_TAG, "Error in getTranslator", ex); + } + } + return null; + } + + private void doBindService() { + Connection localConnection = new Connection(); + if (!mContext.bindService(mServiceIntent, localConnection, + Context.BIND_AUTO_CREATE)) { + Log.e(LOG_TAG, "Failed to bind to service"); + mHandler.scheduleRebind(); + return; + } + mConnection = localConnection; + Log.i(LOG_TAG, "Bound to translator service"); + } + + private void doUnbindService() { + if (mConnection != null) { + mContext.unbindService(mConnection); + mConnection = null; + } + } + + private ITranslatorService getTranslatorService() { + Connection localConnection = mConnection; + if (localConnection != null) { + return localConnection.mService; + } + return null; + } + + private class Connection implements ServiceConnection { + // Read in application threads, written in main thread. + private volatile ITranslatorService mService; + + @Override + public void onServiceConnected(ComponentName className, + IBinder binder) { + Log.i(LOG_TAG, "Connected to translation service"); + ITranslatorService localService = + ITranslatorService.Stub.asInterface(binder); + try { + localService.setCallback(mServiceCallback); + mService = localService; + synchronized (mHandler) { + mNumFailedBinds = 0; + } + } catch (RemoteException ex) { + // Service went away, rely on disconnect handler to + // schedule a rebind. + Log.e(LOG_TAG, "Error when setting callback", ex); + } + } + + @Override + public void onServiceDisconnected(ComponentName className) { + Log.e(LOG_TAG, "Disconnected from translator service"); + mService = null; + // Retry by rebinding, and finally call the onInit if aplicable. + mHandler.scheduleRebind(); + } + } + + private class BrailleTranslatorImpl implements BrailleTranslator { + private final String mTable; + + public BrailleTranslatorImpl(String table) { + mTable = table; + } + + @Override + public byte[] translate(String text) { + ITranslatorService localService = getTranslatorService(); + if (localService != null) { + try { + return localService.translate(text, mTable); + } catch (RemoteException ex) { + Log.e(LOG_TAG, "Error in translate", ex); + } + } + return null; + } + + @Override + public String backTranslate(byte[] cells) { + ITranslatorService localService = getTranslatorService(); + if (localService != null) { + try { + return localService.backTranslate(cells, mTable); + } catch (RemoteException ex) { + Log.e(LOG_TAG, "Error in backTranslate", ex); + } + } + return null; + } + } + + private class ServiceCallback extends ITranslatorServiceCallback.Stub { + @Override + public void onInit(int status) { + mHandler.onInit(status); + } + } + + private class TranslatorManagerHandler extends Handler { + private static final int MSG_ON_INIT = 1; + private static final int MSG_REBIND_SERVICE = 2; + + public void onInit(int status) { + obtainMessage(MSG_ON_INIT, status, 0).sendToTarget(); + } + + public void destroy() { + mOnInitListener = null; + // Cacnel outstanding messages, most importantly + // scheduled rebinds. + removeCallbacksAndMessages(null); + } + + public void scheduleRebind() { + synchronized (this) { + if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) { + int delay = REBIND_DELAY_MILLIS << mNumFailedBinds; + sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay); + ++mNumFailedBinds; + } else { + onInit(ERROR); + } + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ON_INIT: + handleOnInit(msg.arg1); + break; + case MSG_REBIND_SERVICE: + handleRebindService(); + break; + } + } + + private void handleOnInit(int status) { + if (mOnInitListener != null) { + mOnInitListener.onInit(status); + mOnInitListener = null; + } + } + + private void handleRebindService() { + if (mConnection != null) { + doUnbindService(); + } + doBindService(); + } + } +} |