summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-02-23 15:42:10 -0800
committerJeff Sharkey <jsharkey@android.com>2013-02-23 15:48:24 -0800
commitde15e79aadde33fd8c880c19bd4fc6caca0bf795 (patch)
treed6f5867abab2baaf174e2066c5389971c3681e9d /src
parentcedf158c17dc147163734ad1070032ff934d1b2e (diff)
downloadandroid_packages_apps_Terminal-de15e79aadde33fd8c880c19bd4fc6caca0bf795.tar.gz
android_packages_apps_Terminal-de15e79aadde33fd8c880c19bd4fc6caca0bf795.tar.bz2
android_packages_apps_Terminal-de15e79aadde33fd8c880c19bd4fc6caca0bf795.zip
Service to host long-lived terminals, tab UI.
Bind to new TerminalService when UI is running, and keep service started as long as terminals are active. Use ViewPager to show multiple active terminals, and menu items to open/close terminals. Anti-alias terminal text. Reduce callback logging. Add method to stop a running shell; still need to kill child process. Change-Id: I8efcb43aeaf8813762cd0ceebcd5388fc51ebaab
Diffstat (limited to 'src')
-rw-r--r--src/com/android/terminal/Terminal.java17
-rw-r--r--src/com/android/terminal/TerminalActivity.java141
-rw-r--r--src/com/android/terminal/TerminalService.java73
-rw-r--r--src/com/android/terminal/TerminalView.java41
4 files changed, 258 insertions, 14 deletions
diff --git a/src/com/android/terminal/Terminal.java b/src/com/android/terminal/Terminal.java
index d37e6a8..c9e45e7 100644
--- a/src/com/android/terminal/Terminal.java
+++ b/src/com/android/terminal/Terminal.java
@@ -24,6 +24,8 @@ import android.graphics.Color;
public class Terminal {
private static final String TAG = "Terminal";
+ private static int sNumber = 0;
+
static {
System.loadLibrary("jni_terminal");
}
@@ -58,6 +60,8 @@ public class Terminal {
private final int mNativePtr;
private final Thread mThread;
+ private String mTitle;
+
private TerminalClient mClient;
private final TerminalCallbacks mCallbacks = new TerminalCallbacks() {
@@ -90,6 +94,7 @@ public class Terminal {
public Terminal() {
mNativePtr = nativeInit(mCallbacks, 25, 80);
+ mTitle = TAG + " " + sNumber++;
mThread = new Thread(TAG) {
@Override
public void run() {
@@ -105,6 +110,12 @@ public class Terminal {
mThread.start();
}
+ public void stop() {
+ if (nativeStop(mNativePtr) != 0) {
+ throw new IllegalStateException("stop failed");
+ }
+ }
+
public void setClient(TerminalClient client) {
mClient = client;
}
@@ -135,8 +146,14 @@ public class Terminal {
}
}
+ public String getTitle() {
+ // TODO: hook up to title passed through termprop
+ return mTitle;
+ }
+
private static native int nativeInit(TerminalCallbacks callbacks, int rows, int cols);
private static native int nativeRun(int ptr);
+ private static native int nativeStop(int ptr);
private static native int nativeFlushDamage(int ptr);
private static native int nativeResize(int ptr, int rows, int cols);
diff --git a/src/com/android/terminal/TerminalActivity.java b/src/com/android/terminal/TerminalActivity.java
index bef1859..99d2be6 100644
--- a/src/com/android/terminal/TerminalActivity.java
+++ b/src/com/android/terminal/TerminalActivity.java
@@ -17,23 +17,154 @@
package com.android.terminal;
import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.PagerTitleStrip;
+import android.support.v4.view.ViewPager;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+/**
+ * Activity that displays all {@link Terminal} instances running in a bound
+ * {@link TerminalService}.
+ */
public class TerminalActivity extends Activity {
private static final String TAG = "Terminal";
+ private TerminalService mService;
+
+ private ViewPager mPager;
+ private PagerTitleStrip mTitles;
+
+ private final ServiceConnection mServiceConn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = ((TerminalService.ServiceBinder) service).getService();
+
+ final int size = mService.getTerminals().size();
+ Log.d(TAG, "Bound to service with " + size + " active terminals");
+
+ // Give ourselves at least one terminal session
+ if (size == 0) {
+ mService.createTerminal();
+ }
+
+ // Bind UI to known terminals
+ mTermAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ throw new RuntimeException("Service in same process disconnected?");
+ }
+ };
+
+ private final PagerAdapter mTermAdapter = new PagerAdapter() {
+ @Override
+ public int getCount() {
+ if (mService != null) {
+ return mService.getTerminals().size();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ final Terminal term = mService.getTerminals().get(position);
+ final TerminalView view = new TerminalView(container.getContext());
+ view.setTerminal(term);
+ container.addView(view);
+ return view;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ final TerminalView view = (TerminalView) object;
+ view.setTerminal(null);
+ container.removeView(view);
+ }
+
+ @Override
+ public int getItemPosition(Object object) {
+ final int index = mService.getTerminals().indexOf(object);
+ if (index == -1) {
+ return POSITION_NONE;
+ } else {
+ return index;
+ }
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mService.getTerminals().get(position).getTitle();
+ }
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- final Terminal term = new Terminal();
- term.start();
+ setContentView(R.layout.activity);
+
+ mPager = (ViewPager) findViewById(R.id.pager);
+ mTitles = (PagerTitleStrip) findViewById(R.id.titles);
+
+ mPager.setAdapter(mTermAdapter);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ bindService(
+ new Intent(this, TerminalService.class), mServiceConn, Context.BIND_AUTO_CREATE);
+ }
- final TerminalView view = new TerminalView(this, term);
+ @Override
+ protected void onStop() {
+ super.onStop();
+ unbindService(mServiceConn);
+ }
- setContentView(view);
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.activity, menu);
+ return true;
+ }
- Log.d(TAG, "Rows: " + term.getRows());
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_new_tab: {
+ mService.createTerminal();
+ mTermAdapter.notifyDataSetChanged();
+ final int index = mService.getTerminals().size() - 1;
+ mPager.setCurrentItem(index, true);
+ return true;
+ }
+ case R.id.menu_close_tab: {
+ final int index = mPager.getCurrentItem();
+ final Terminal term = mService.getTerminals().get(index);
+ mService.destroyTerminal(term);
+ // TODO: ask adamp about buggy zero item behavior
+ mTermAdapter.notifyDataSetChanged();
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/src/com/android/terminal/TerminalService.java b/src/com/android/terminal/TerminalService.java
new file mode 100644
index 0000000..ebbdcce
--- /dev/null
+++ b/src/com/android/terminal/TerminalService.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2013 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.terminal;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Background service that keeps {@link Terminal} instances running and warm
+ * when UI isn't present.
+ */
+public class TerminalService extends Service {
+ private static final String TAG = "Terminal";
+
+ private final ArrayList<Terminal> mTerminals = new ArrayList<Terminal>();
+
+ public class ServiceBinder extends Binder {
+ public TerminalService getService() {
+ return TerminalService.this;
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new ServiceBinder();
+ }
+
+ public List<Terminal> getTerminals() {
+ return Collections.unmodifiableList(mTerminals);
+ }
+
+ public Terminal createTerminal() {
+ // If our first terminal, start ourselves as long-lived service
+ if (mTerminals.isEmpty()) {
+ startService(new Intent(this, TerminalService.class));
+ }
+
+ final Terminal term = new Terminal();
+ term.start();
+ mTerminals.add(term);
+ return term;
+ }
+
+ public void destroyTerminal(Terminal term) {
+ term.stop();
+ mTerminals.remove(term);
+
+ // If our last terminal, tear down long-lived service
+ if (mTerminals.isEmpty()) {
+ stopService(new Intent(this, TerminalService.class));
+ }
+ }
+}
diff --git a/src/com/android/terminal/TerminalView.java b/src/com/android/terminal/TerminalView.java
index 35192e9..64aeabd 100644
--- a/src/com/android/terminal/TerminalView.java
+++ b/src/com/android/terminal/TerminalView.java
@@ -35,11 +35,11 @@ import com.android.terminal.Terminal.TerminalClient;
*/
public class TerminalView extends View {
private static final String TAG = "Terminal";
+ private static final boolean LOGD = true;
private static final int MAX_RUN_LENGTH = 128;
private final Context mContext;
- private final Terminal mTerm;
private final Paint mBgPaint = new Paint();
private final Paint mTextPaint = new Paint();
@@ -49,6 +49,8 @@ public class TerminalView extends View {
/** Screen coordinates to draw chars into */
private final float[] mPos;
+ private Terminal mTerm;
+
private int mCharTop;
private int mCharWidth;
private int mCharHeight;
@@ -59,7 +61,7 @@ public class TerminalView extends View {
private TerminalClient mClient = new TerminalClient() {
@Override
public void damage(int startRow, int endRow, int startCol, int endCol) {
- Log.d(TAG, "damage(" + startRow + ", " + endRow + ", " + startCol + ", " + endCol + ")");
+ if (LOGD) Log.d(TAG, "damage(" + startRow + ", " + endRow + ", " + startCol + ", " + endCol + ")");
// Invalidate region on screen
final int top = startRow * mCharHeight;
@@ -86,10 +88,9 @@ public class TerminalView extends View {
}
};
- public TerminalView(Context context, Terminal term) {
+ public TerminalView(Context context) {
super(context);
mContext = context;
- mTerm = term;
mRun = new Terminal.CellRun();
mRun.data = new char[MAX_RUN_LENGTH];
@@ -110,20 +111,37 @@ public class TerminalView extends View {
});
}
+ public void setTerminal(Terminal term) {
+ final Terminal orig = mTerm;
+ if (orig != null) {
+ orig.setClient(null);
+ }
+ mTerm = term;
+ if (term != null) {
+ term.setClient(mClient);
+ }
+ updateTerminalSize();
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mTerm.setClient(mClient);
+ if (mTerm != null) {
+ mTerm.setClient(mClient);
+ }
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mTerm.setClient(null);
+ if (mTerm != null) {
+ mTerm.setClient(null);
+ }
}
public void setTextSize(float textSize) {
mTextPaint.setTypeface(Typeface.MONOSPACE);
+ mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(textSize);
// Read metrics to get exact pixel dimensions
@@ -149,11 +167,10 @@ public class TerminalView extends View {
* and request that {@link Terminal} change to that size.
*/
public void updateTerminalSize() {
- if (getWidth() > 0 && getHeight() > 0) {
+ if (getWidth() > 0 && getHeight() > 0 && mTerm != null) {
final int rows = getHeight() / mCharHeight;
final int cols = getWidth() / mCharWidth;
mTerm.resize(rows, cols);
- mTerm.flushDamage();
}
}
@@ -169,6 +186,12 @@ public class TerminalView extends View {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
+ if (mTerm == null) {
+ Log.w(TAG, "onDraw() without a terminal");
+ canvas.drawColor(Color.MAGENTA);
+ return;
+ }
+
final long start = SystemClock.elapsedRealtime();
// Only draw dirty region of console
@@ -210,6 +233,6 @@ public class TerminalView extends View {
}
final long delta = SystemClock.elapsedRealtime() - start;
- Log.d(TAG, "onDraw() took " + delta + "ms");
+ if (LOGD) Log.d(TAG, "onDraw() took " + delta + "ms");
}
}