1/*
2 * ConnectBot: simple, powerful, open-source SSH client for Android
3 * Copyright 2007 Kenny Root, Jeffrey Sharkey
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package org.connectbot.service;
19
20import android.content.Context;
21import android.content.SharedPreferences;
22import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.Paint;
27import android.graphics.Typeface;
28import android.graphics.Bitmap.Config;
29import android.graphics.Paint.FontMetrics;
30import android.text.ClipboardManager;
31import android.view.ContextMenu;
32import android.view.Menu;
33import android.view.View;
34import android.view.ContextMenu.ContextMenuInfo;
35
36import com.googlecode.android_scripting.Log;
37import com.googlecode.android_scripting.R;
38import com.googlecode.android_scripting.facade.ui.UiFacade;
39import com.googlecode.android_scripting.interpreter.InterpreterProcess;
40import com.googlecode.android_scripting.jsonrpc.RpcReceiverManager;
41import com.googlecode.android_scripting.jsonrpc.RpcReceiverManagerFactory;
42
43import de.mud.terminal.VDUBuffer;
44import de.mud.terminal.VDUDisplay;
45import de.mud.terminal.vt320;
46
47import java.io.IOException;
48import java.nio.charset.Charset;
49import java.util.LinkedList;
50import java.util.List;
51
52import org.connectbot.TerminalView;
53import org.connectbot.transport.AbsTransport;
54import org.connectbot.util.Colors;
55import org.connectbot.util.PreferenceConstants;
56import org.connectbot.util.SelectionArea;
57
58/**
59 * Provides a bridge between a MUD terminal buffer and a possible TerminalView. This separation
60 * allows us to keep the TerminalBridge running in a background service. A TerminalView shares down
61 * a bitmap that we can use for rendering when available.
62 *
63 * @author ConnectBot Dev Team
64 * @author raaar
65 *
66 */
67public class TerminalBridge implements VDUDisplay, OnSharedPreferenceChangeListener {
68
69  private final static int FONT_SIZE_STEP = 2;
70
71  private final int[] color = new int[Colors.defaults.length];
72
73  private final TerminalManager manager;
74
75  private final InterpreterProcess mProcess;
76
77  private int mDefaultFgColor;
78  private int mDefaultBgColor;
79
80  private int scrollback;
81
82  private String delKey;
83  private String encoding;
84
85  private AbsTransport transport;
86
87  private final Paint defaultPaint;
88
89  private Relay relay;
90
91  private Bitmap bitmap = null;
92  private final VDUBuffer buffer;
93
94  private TerminalView parent = null;
95  private final Canvas canvas = new Canvas();
96
97  private boolean forcedSize = false;
98  private int columns;
99  private int rows;
100
101  private final TerminalKeyListener keyListener;
102
103  private boolean selectingForCopy = false;
104  private final SelectionArea selectionArea;
105  private ClipboardManager clipboard;
106
107  public int charWidth = -1;
108  public int charHeight = -1;
109  private int charTop = -1;
110
111  private float fontSize = -1;
112
113  private final List<FontSizeChangedListener> fontSizeChangedListeners;
114
115  /**
116   * Flag indicating if we should perform a full-screen redraw during our next rendering pass.
117   */
118  private boolean fullRedraw = false;
119
120  private final PromptHelper promptHelper;
121
122  /**
123   * Create a new terminal bridge suitable for unit testing.
124   */
125  public TerminalBridge() {
126    buffer = new vt320() {
127      @Override
128      public void write(byte[] b) {
129      }
130
131      @Override
132      public void write(int b) {
133      }
134
135      @Override
136      public void sendTelnetCommand(byte cmd) {
137      }
138
139      @Override
140      public void setWindowSize(int c, int r) {
141      }
142
143      @Override
144      public void debug(String s) {
145      }
146    };
147
148    manager = null;
149
150    defaultPaint = new Paint();
151
152    selectionArea = new SelectionArea();
153    scrollback = 1;
154
155    fontSizeChangedListeners = new LinkedList<FontSizeChangedListener>();
156
157    transport = null;
158
159    keyListener = new TerminalKeyListener(manager, this, buffer, null);
160
161    mProcess = null;
162
163    mDefaultFgColor = 0;
164    mDefaultBgColor = 0;
165    promptHelper = null;
166
167    updateCharset();
168  }
169
170  /**
171   * Create new terminal bridge with following parameters.
172   */
173  public TerminalBridge(final TerminalManager manager, InterpreterProcess process, AbsTransport t)
174      throws IOException {
175    this.manager = manager;
176    transport = t;
177    mProcess = process;
178
179    String string = manager.getStringParameter(PreferenceConstants.SCROLLBACK, null);
180    if (string != null) {
181      scrollback = Integer.parseInt(string);
182    } else {
183      scrollback = PreferenceConstants.DEFAULT_SCROLLBACK;
184    }
185
186    string = manager.getStringParameter(PreferenceConstants.FONTSIZE, null);
187    if (string != null) {
188      fontSize = Float.parseFloat(string);
189    } else {
190      fontSize = PreferenceConstants.DEFAULT_FONT_SIZE;
191    }
192
193    mDefaultFgColor =
194        manager.getIntParameter(PreferenceConstants.COLOR_FG, PreferenceConstants.DEFAULT_FG_COLOR);
195    mDefaultBgColor =
196        manager.getIntParameter(PreferenceConstants.COLOR_BG, PreferenceConstants.DEFAULT_BG_COLOR);
197
198    delKey = manager.getStringParameter(PreferenceConstants.DELKEY, PreferenceConstants.DELKEY_DEL);
199
200    // create prompt helper to relay password and hostkey requests up to gui
201    promptHelper = new PromptHelper(this);
202
203    // create our default paint
204    defaultPaint = new Paint();
205    defaultPaint.setAntiAlias(true);
206    defaultPaint.setTypeface(Typeface.MONOSPACE);
207    defaultPaint.setFakeBoldText(true); // more readable?
208
209    fontSizeChangedListeners = new LinkedList<FontSizeChangedListener>();
210
211    setFontSize(fontSize);
212
213    // create terminal buffer and handle outgoing data
214    // this is probably status reply information
215    buffer = new vt320() {
216      @Override
217      public void debug(String s) {
218        Log.d(s);
219      }
220
221      @Override
222      public void write(byte[] b) {
223        try {
224          if (b != null && transport != null) {
225            transport.write(b);
226          }
227        } catch (IOException e) {
228          Log.e("Problem writing outgoing data in vt320() thread", e);
229        }
230      }
231
232      @Override
233      public void write(int b) {
234        try {
235          if (transport != null) {
236            transport.write(b);
237          }
238        } catch (IOException e) {
239          Log.e("Problem writing outgoing data in vt320() thread", e);
240        }
241      }
242
243      // We don't use telnet sequences.
244      @Override
245      public void sendTelnetCommand(byte cmd) {
246      }
247
248      // We don't want remote to resize our window.
249      @Override
250      public void setWindowSize(int c, int r) {
251      }
252
253      @Override
254      public void beep() {
255        if (parent.isShown()) {
256          manager.playBeep();
257        }
258      }
259    };
260
261    // Don't keep any scrollback if a session is not being opened.
262
263    buffer.setBufferSize(scrollback);
264
265    resetColors();
266    buffer.setDisplay(this);
267
268    selectionArea = new SelectionArea();
269
270    keyListener = new TerminalKeyListener(manager, this, buffer, encoding);
271
272    updateCharset();
273
274    manager.registerOnSharedPreferenceChangeListener(this);
275
276  }
277
278  /**
279   * Spawn thread to open connection and start login process.
280   */
281  protected void connect() {
282    transport.setBridge(this);
283    transport.setManager(manager);
284    transport.connect();
285
286    ((vt320) buffer).reset();
287
288    // previously tried vt100 and xterm for emulation modes
289    // "screen" works the best for color and escape codes
290    ((vt320) buffer).setAnswerBack("screen");
291
292    if (PreferenceConstants.DELKEY_BACKSPACE.equals(delKey)) {
293      ((vt320) buffer).setBackspace(vt320.DELETE_IS_BACKSPACE);
294    } else {
295      ((vt320) buffer).setBackspace(vt320.DELETE_IS_DEL);
296    }
297
298    // create thread to relay incoming connection data to buffer
299    relay = new Relay(this, transport, (vt320) buffer, encoding);
300    Thread relayThread = new Thread(relay);
301    relayThread.setDaemon(true);
302    relayThread.setName("Relay");
303    relayThread.start();
304
305    // force font-size to make sure we resizePTY as needed
306    setFontSize(fontSize);
307
308  }
309
310  private void updateCharset() {
311    encoding =
312        manager.getStringParameter(PreferenceConstants.ENCODING, Charset.defaultCharset().name());
313    if (relay != null) {
314      relay.setCharset(encoding);
315    }
316    keyListener.setCharset(encoding);
317  }
318
319  /**
320   * Inject a specific string into this terminal. Used for post-login strings and pasting clipboard.
321   */
322  public void injectString(final String string) {
323    if (string == null || string.length() == 0) {
324      return;
325    }
326
327    Thread injectStringThread = new Thread(new Runnable() {
328      public void run() {
329        try {
330          transport.write(string.getBytes(encoding));
331        } catch (Exception e) {
332          Log.e("Couldn't inject string to remote host: ", e);
333        }
334      }
335    });
336    injectStringThread.setName("InjectString");
337    injectStringThread.start();
338  }
339
340  /**
341   * @return whether a session is open or not
342   */
343  public boolean isSessionOpen() {
344    if (transport != null) {
345      return transport.isSessionOpen();
346    }
347    return false;
348  }
349
350  /**
351   * Force disconnection of this terminal bridge.
352   */
353  public void dispatchDisconnect(boolean immediate) {
354
355    // Cancel any pending prompts.
356    promptHelper.cancelPrompt();
357
358    if (immediate) {
359      manager.closeConnection(TerminalBridge.this, true);
360    } else {
361      Thread disconnectPromptThread = new Thread(new Runnable() {
362        public void run() {
363          String prompt = null;
364          if (transport != null && transport.isConnected()) {
365            prompt = manager.getResources().getString(R.string.prompt_confirm_exit);
366          } else {
367            prompt = manager.getResources().getString(R.string.prompt_process_exited);
368          }
369          Boolean result = promptHelper.requestBooleanPrompt(null, prompt);
370
371          if (transport != null && transport.isConnected()) {
372            manager.closeConnection(TerminalBridge.this, result != null && result.booleanValue());
373          } else if (result != null && result.booleanValue()) {
374            manager.closeConnection(TerminalBridge.this, false);
375          }
376        }
377      });
378      disconnectPromptThread.setName("DisconnectPrompt");
379      disconnectPromptThread.setDaemon(true);
380      disconnectPromptThread.start();
381    }
382  }
383
384  public void setSelectingForCopy(boolean selectingForCopy) {
385    this.selectingForCopy = selectingForCopy;
386  }
387
388  public boolean isSelectingForCopy() {
389    return selectingForCopy;
390  }
391
392  public SelectionArea getSelectionArea() {
393    return selectionArea;
394  }
395
396  public synchronized void tryKeyVibrate() {
397    manager.tryKeyVibrate();
398  }
399
400  /**
401   * Request a different font size. Will make call to parentChanged() to make sure we resize PTY if
402   * needed.
403   */
404  /* package */final void setFontSize(float size) {
405    if (size <= 0.0) {
406      return;
407    }
408
409    defaultPaint.setTextSize(size);
410    fontSize = size;
411
412    // read new metrics to get exact pixel dimensions
413    FontMetrics fm = defaultPaint.getFontMetrics();
414    charTop = (int) Math.ceil(fm.top);
415
416    float[] widths = new float[1];
417    defaultPaint.getTextWidths("X", widths);
418    charWidth = (int) Math.ceil(widths[0]);
419    charHeight = (int) Math.ceil(fm.descent - fm.top);
420
421    // refresh any bitmap with new font size
422    if (parent != null) {
423      parentChanged(parent);
424    }
425
426    for (FontSizeChangedListener ofscl : fontSizeChangedListeners) {
427      ofscl.onFontSizeChanged(size);
428    }
429    forcedSize = false;
430  }
431
432  /**
433   * Add an {@link FontSizeChangedListener} to the list of listeners for this bridge.
434   *
435   * @param listener
436   *          listener to add
437   */
438  public void addFontSizeChangedListener(FontSizeChangedListener listener) {
439    fontSizeChangedListeners.add(listener);
440  }
441
442  /**
443   * Remove an {@link FontSizeChangedListener} from the list of listeners for this bridge.
444   *
445   * @param listener
446   */
447  public void removeFontSizeChangedListener(FontSizeChangedListener listener) {
448    fontSizeChangedListeners.remove(listener);
449  }
450
451  /**
452   * Something changed in our parent {@link TerminalView}, maybe it's a new parent, or maybe it's an
453   * updated font size. We should recalculate terminal size information and request a PTY resize.
454   */
455  public final synchronized void parentChanged(TerminalView parent) {
456    if (manager != null && !manager.isResizeAllowed()) {
457      Log.d("Resize is not allowed now");
458      return;
459    }
460
461    this.parent = parent;
462    final int width = parent.getWidth();
463    final int height = parent.getHeight();
464
465    // Something has gone wrong with our layout; we're 0 width or height!
466    if (width <= 0 || height <= 0) {
467      return;
468    }
469
470    clipboard = (ClipboardManager) parent.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
471    keyListener.setClipboardManager(clipboard);
472
473    if (!forcedSize) {
474      // recalculate buffer size
475      int newColumns, newRows;
476
477      newColumns = width / charWidth;
478      newRows = height / charHeight;
479
480      // If nothing has changed in the terminal dimensions and not an intial
481      // draw then don't blow away scroll regions and such.
482      if (newColumns == columns && newRows == rows) {
483        return;
484      }
485
486      columns = newColumns;
487      rows = newRows;
488    }
489
490    // reallocate new bitmap if needed
491    boolean newBitmap = (bitmap == null);
492    if (bitmap != null) {
493      newBitmap = (bitmap.getWidth() != width || bitmap.getHeight() != height);
494    }
495
496    if (newBitmap) {
497      discardBitmap();
498      bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
499      canvas.setBitmap(bitmap);
500    }
501
502    // clear out any old buffer information
503    defaultPaint.setColor(Color.BLACK);
504    canvas.drawPaint(defaultPaint);
505
506    // Stroke the border of the terminal if the size is being forced;
507    if (forcedSize) {
508      int borderX = (columns * charWidth) + 1;
509      int borderY = (rows * charHeight) + 1;
510
511      defaultPaint.setColor(Color.GRAY);
512      defaultPaint.setStrokeWidth(0.0f);
513      if (width >= borderX) {
514        canvas.drawLine(borderX, 0, borderX, borderY + 1, defaultPaint);
515      }
516      if (height >= borderY) {
517        canvas.drawLine(0, borderY, borderX + 1, borderY, defaultPaint);
518      }
519    }
520
521    try {
522      // request a terminal pty resize
523      synchronized (buffer) {
524        buffer.setScreenSize(columns, rows, true);
525      }
526
527      if (transport != null) {
528        transport.setDimensions(columns, rows, width, height);
529      }
530    } catch (Exception e) {
531      Log.e("Problem while trying to resize screen or PTY", e);
532    }
533
534    // force full redraw with new buffer size
535    fullRedraw = true;
536    redraw();
537
538    parent.notifyUser(String.format("%d x %d", columns, rows));
539
540    Log.i(String.format("parentChanged() now width=%d, height=%d", columns, rows));
541  }
542
543  /**
544   * Somehow our parent {@link TerminalView} was destroyed. Now we don't need to redraw anywhere,
545   * and we can recycle our internal bitmap.
546   */
547  public synchronized void parentDestroyed() {
548    parent = null;
549    discardBitmap();
550  }
551
552  private void discardBitmap() {
553    if (bitmap != null) {
554      bitmap.recycle();
555    }
556    bitmap = null;
557  }
558
559  public void onDraw() {
560    int fg, bg;
561    synchronized (buffer) {
562      boolean entireDirty = buffer.update[0] || fullRedraw;
563      boolean isWideCharacter = false;
564
565      // walk through all lines in the buffer
566      for (int l = 0; l < buffer.height; l++) {
567
568        // check if this line is dirty and needs to be repainted
569        // also check for entire-buffer dirty flags
570        if (!entireDirty && !buffer.update[l + 1]) {
571          continue;
572        }
573
574        // reset dirty flag for this line
575        buffer.update[l + 1] = false;
576
577        // walk through all characters in this line
578        for (int c = 0; c < buffer.width; c++) {
579          int addr = 0;
580          int currAttr = buffer.charAttributes[buffer.windowBase + l][c];
581          // check if foreground color attribute is set
582          if ((currAttr & VDUBuffer.COLOR_FG) != 0) {
583            int fgcolor = ((currAttr & VDUBuffer.COLOR_FG) >> VDUBuffer.COLOR_FG_SHIFT) - 1;
584            if (fgcolor < 8 && (currAttr & VDUBuffer.BOLD) != 0) {
585              fg = color[fgcolor + 8];
586            } else {
587              fg = color[fgcolor];
588            }
589          } else {
590            fg = mDefaultFgColor;
591          }
592
593          // check if background color attribute is set
594          if ((currAttr & VDUBuffer.COLOR_BG) != 0) {
595            bg = color[((currAttr & VDUBuffer.COLOR_BG) >> VDUBuffer.COLOR_BG_SHIFT) - 1];
596          } else {
597            bg = mDefaultBgColor;
598          }
599
600          // support character inversion by swapping background and foreground color
601          if ((currAttr & VDUBuffer.INVERT) != 0) {
602            int swapc = bg;
603            bg = fg;
604            fg = swapc;
605          }
606
607          // set underlined attributes if requested
608          defaultPaint.setUnderlineText((currAttr & VDUBuffer.UNDERLINE) != 0);
609
610          isWideCharacter = (currAttr & VDUBuffer.FULLWIDTH) != 0;
611
612          if (isWideCharacter) {
613            addr++;
614          } else {
615            // determine the amount of continuous characters with the same settings and print them
616            // all at once
617            while (c + addr < buffer.width
618                && buffer.charAttributes[buffer.windowBase + l][c + addr] == currAttr) {
619              addr++;
620            }
621          }
622
623          // Save the current clip region
624          canvas.save(Canvas.CLIP_SAVE_FLAG);
625
626          // clear this dirty area with background color
627          defaultPaint.setColor(bg);
628          if (isWideCharacter) {
629            canvas.clipRect(c * charWidth, l * charHeight, (c + 2) * charWidth, (l + 1)
630                * charHeight);
631          } else {
632            canvas.clipRect(c * charWidth, l * charHeight, (c + addr) * charWidth, (l + 1)
633                * charHeight);
634          }
635          canvas.drawPaint(defaultPaint);
636
637          // write the text string starting at 'c' for 'addr' number of characters
638          defaultPaint.setColor(fg);
639          if ((currAttr & VDUBuffer.INVISIBLE) == 0) {
640            canvas.drawText(buffer.charArray[buffer.windowBase + l], c, addr, c * charWidth,
641                (l * charHeight) - charTop, defaultPaint);
642          }
643
644          // Restore the previous clip region
645          canvas.restore();
646
647          // advance to the next text block with different characteristics
648          c += addr - 1;
649          if (isWideCharacter) {
650            c++;
651          }
652        }
653      }
654
655      // reset entire-buffer flags
656      buffer.update[0] = false;
657    }
658    fullRedraw = false;
659  }
660
661  public void redraw() {
662    if (parent != null) {
663      parent.postInvalidate();
664    }
665  }
666
667  // We don't have a scroll bar.
668  public void updateScrollBar() {
669  }
670
671  /**
672   * Resize terminal to fit [rows]x[cols] in screen of size [width]x[height]
673   *
674   * @param rows
675   * @param cols
676   * @param width
677   * @param height
678   */
679  public synchronized void resizeComputed(int cols, int rows, int width, int height) {
680    float size = 8.0f;
681    float step = 8.0f;
682    float limit = 0.125f;
683
684    int direction;
685
686    while ((direction = fontSizeCompare(size, cols, rows, width, height)) < 0) {
687      size += step;
688    }
689
690    if (direction == 0) {
691      Log.d(String.format("Fontsize: found match at %f", size));
692      return;
693    }
694
695    step /= 2.0f;
696    size -= step;
697
698    while ((direction = fontSizeCompare(size, cols, rows, width, height)) != 0 && step >= limit) {
699      step /= 2.0f;
700      if (direction > 0) {
701        size -= step;
702      } else {
703        size += step;
704      }
705    }
706
707    if (direction > 0) {
708      size -= step;
709    }
710
711    columns = cols;
712    this.rows = rows;
713    setFontSize(size);
714    forcedSize = true;
715  }
716
717  private int fontSizeCompare(float size, int cols, int rows, int width, int height) {
718    // read new metrics to get exact pixel dimensions
719    defaultPaint.setTextSize(size);
720    FontMetrics fm = defaultPaint.getFontMetrics();
721
722    float[] widths = new float[1];
723    defaultPaint.getTextWidths("X", widths);
724    int termWidth = (int) widths[0] * cols;
725    int termHeight = (int) Math.ceil(fm.descent - fm.top) * rows;
726
727    Log.d(String.format("Fontsize: font size %f resulted in %d x %d", size, termWidth, termHeight));
728
729    // Check to see if it fits in resolution specified.
730    if (termWidth > width || termHeight > height) {
731      return 1;
732    }
733
734    if (termWidth == width || termHeight == height) {
735      return 0;
736    }
737
738    return -1;
739  }
740
741  /*
742   * (non-Javadoc)
743   *
744   * @see de.mud.terminal.VDUDisplay#setVDUBuffer(de.mud.terminal.VDUBuffer)
745   */
746  @Override
747  public void setVDUBuffer(VDUBuffer buffer) {
748  }
749
750  /*
751   * (non-Javadoc)
752   *
753   * @see de.mud.terminal.VDUDisplay#setColor(byte, byte, byte, byte)
754   */
755  public void setColor(int index, int red, int green, int blue) {
756    // Don't allow the system colors to be overwritten for now. May violate specs.
757    if (index < color.length && index >= 16) {
758      color[index] = 0xff000000 | red << 16 | green << 8 | blue;
759    }
760  }
761
762  public final void resetColors() {
763    System.arraycopy(Colors.defaults, 0, color, 0, Colors.defaults.length);
764  }
765
766  public TerminalKeyListener getKeyHandler() {
767    return keyListener;
768  }
769
770  public void resetScrollPosition() {
771    // if we're in scrollback, scroll to bottom of window on input
772    if (buffer.windowBase != buffer.screenBase) {
773      buffer.setWindowBase(buffer.screenBase);
774    }
775  }
776
777  public void increaseFontSize() {
778    setFontSize(fontSize + FONT_SIZE_STEP);
779  }
780
781  public void decreaseFontSize() {
782    setFontSize(fontSize - FONT_SIZE_STEP);
783  }
784
785  public int getId() {
786    return mProcess.getPort();
787  }
788
789  public String getName() {
790    return mProcess.getName();
791  }
792
793  public InterpreterProcess getProcess() {
794    return mProcess;
795  }
796
797  public int getForegroundColor() {
798    return mDefaultFgColor;
799  }
800
801  public int getBackgroundColor() {
802    return mDefaultBgColor;
803  }
804
805  public VDUBuffer getVDUBuffer() {
806    return buffer;
807  }
808
809  public PromptHelper getPromptHelper() {
810    return promptHelper;
811  }
812
813  public Bitmap getBitmap() {
814    return bitmap;
815  }
816
817  public AbsTransport getTransport() {
818    return transport;
819  }
820
821  public Paint getPaint() {
822    return defaultPaint;
823  }
824
825  public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
826    if (mProcess.isAlive()) {
827      RpcReceiverManagerFactory rpcReceiverManagerFactory = mProcess.getRpcReceiverManagerFactory();
828      for (RpcReceiverManager manager : rpcReceiverManagerFactory.getRpcReceiverManagers().values()) {
829        UiFacade facade = manager.getReceiver(UiFacade.class);
830        facade.onCreateContextMenu(menu, v, menuInfo);
831      }
832    }
833  }
834
835  public boolean onPrepareOptionsMenu(Menu menu) {
836    boolean returnValue = false;
837    if (mProcess.isAlive()) {
838      RpcReceiverManagerFactory rpcReceiverManagerFactory = mProcess.getRpcReceiverManagerFactory();
839      for (RpcReceiverManager manager : rpcReceiverManagerFactory.getRpcReceiverManagers().values()) {
840        UiFacade facade = manager.getReceiver(UiFacade.class);
841        returnValue = returnValue || facade.onPrepareOptionsMenu(menu);
842      }
843      return returnValue;
844    }
845    return false;
846  }
847
848  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
849    if (PreferenceConstants.ENCODING.equals(key)) {
850      updateCharset();
851    } else if (PreferenceConstants.FONTSIZE.equals(key)) {
852      String string = manager.getStringParameter(PreferenceConstants.FONTSIZE, null);
853      if (string != null) {
854        fontSize = Float.parseFloat(string);
855      } else {
856        fontSize = PreferenceConstants.DEFAULT_FONT_SIZE;
857      }
858      setFontSize(fontSize);
859      fullRedraw = true;
860    } else if (PreferenceConstants.SCROLLBACK.equals(key)) {
861      String string = manager.getStringParameter(PreferenceConstants.SCROLLBACK, null);
862      if (string != null) {
863        scrollback = Integer.parseInt(string);
864      } else {
865        scrollback = PreferenceConstants.DEFAULT_SCROLLBACK;
866      }
867      buffer.setBufferSize(scrollback);
868    } else if (PreferenceConstants.COLOR_FG.equals(key)) {
869      mDefaultFgColor =
870          manager.getIntParameter(PreferenceConstants.COLOR_FG,
871              PreferenceConstants.DEFAULT_FG_COLOR);
872      fullRedraw = true;
873    } else if (PreferenceConstants.COLOR_BG.equals(key)) {
874      mDefaultBgColor =
875          manager.getIntParameter(PreferenceConstants.COLOR_BG,
876              PreferenceConstants.DEFAULT_BG_COLOR);
877      fullRedraw = true;
878    }
879    if (PreferenceConstants.DELKEY.equals(key)) {
880      delKey =
881          manager.getStringParameter(PreferenceConstants.DELKEY, PreferenceConstants.DELKEY_DEL);
882      if (PreferenceConstants.DELKEY_BACKSPACE.equals(delKey)) {
883        ((vt320) buffer).setBackspace(vt320.DELETE_IS_BACKSPACE);
884      } else {
885        ((vt320) buffer).setBackspace(vt320.DELETE_IS_DEL);
886      }
887    }
888  }
889}
890