summaryrefslogtreecommitdiff
path: root/translate/TranslatorManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'translate/TranslatorManager.java')
-rw-r--r--translate/TranslatorManager.java278
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();
+ }
+ }
+}