diff options
25 files changed, 923 insertions, 118 deletions
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 30799ec..d435df5 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -116,6 +116,24 @@ public class ConnectivityManager "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; /** + * Broadcast Action: A tetherable connection has come or gone + * TODO - finish the doc + * @hide + */ + public static final String ACTION_TETHER_STATE_CHANGED = + "android.net.conn.TETHER_STATE_CHANGED"; + + /** + * @hide + */ + public static final String EXTRA_AVAILABLE_TETHER_COUNT = "availableCount"; + + /** + * @hide + */ + public static final String EXTRA_ACTIVE_TETHER_COUNT = "activeCount"; + + /** * The Default Mobile data connection. When active, all data traffic * will use this connection by default. Should not coexist with other * default connections. @@ -338,4 +356,48 @@ public class ConnectivityManager } mService = service; } + + /** + * {@hide} + */ + public String[] getTetherableIfaces() { + try { + return mService.getTetherableIfaces(); + } catch (RemoteException e) { + return new String[0]; + } + } + + /** + * {@hide} + */ + public String[] getTetheredIfaces() { + try { + return mService.getTetheredIfaces(); + } catch (RemoteException e) { + return new String[0]; + } + } + + /** + * {@hide} + */ + public boolean tether(String iface) { + try { + return mService.tether(iface); + } catch (RemoteException e) { + return false; + } + } + + /** + * {@hide} + */ + public boolean untether(String iface) { + try { + return mService.untether(iface); + } catch (RemoteException e) { + return false; + } + } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 9f59cce..caa3f2b 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -50,4 +50,12 @@ interface IConnectivityManager boolean getBackgroundDataSetting(); void setBackgroundDataSetting(boolean allowBackgroundData); + + boolean tether(String iface); + + boolean untether(String iface); + + String[] getTetherableIfaces(); + + String[] getTetheredIfaces(); } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index e4ec098..f48f45f 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -140,7 +140,8 @@ interface INetworkManagementService * Attaches a PPP server daemon to the specified TTY with the specified * local/remote addresses. */ - void attachPppd(String tty, String localAddr, String remoteAddr); + void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr, + String dns2Addr); /** * Detaches a PPP server daemon from the specified TTY. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7128005..bacaf43 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2944,6 +2944,13 @@ public final class Settings { public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled"; /** + * Whether or not a notification is displayed when a Tetherable interface is detected. + * (0 = false, 1 = true) + * @hide + */ + public static final String TETHER_NOTIFY = "tether_notify"; + + /** * If nonzero, ANRs in invisible background processes bring up a dialog. * Otherwise, the process will be silently killed. * @hide diff --git a/core/java/android/webkit/CacheLoader.java b/core/java/android/webkit/CacheLoader.java index de8f888..aeb537c 100644 --- a/core/java/android/webkit/CacheLoader.java +++ b/core/java/android/webkit/CacheLoader.java @@ -43,7 +43,7 @@ class CacheLoader extends StreamLoader { protected boolean setupStreamAndSendStatus() { mDataStream = mCacheResult.inStream; mContentLength = mCacheResult.contentLength; - mHandler.status(1, 1, mCacheResult.httpStatusCode, "OK"); + mLoadListener.status(1, 1, mCacheResult.httpStatusCode, "OK"); return true; } diff --git a/core/java/android/webkit/ContentLoader.java b/core/java/android/webkit/ContentLoader.java index 5eb54b0..d13210a 100644 --- a/core/java/android/webkit/ContentLoader.java +++ b/core/java/android/webkit/ContentLoader.java @@ -16,14 +16,10 @@ package android.webkit; -import android.content.Context; import android.net.http.EventHandler; import android.net.http.Headers; import android.net.Uri; -import java.io.File; -import java.io.FileInputStream; - /** * This class is a concrete implementation of StreamLoader that loads * "content:" URIs @@ -68,7 +64,7 @@ class ContentLoader extends StreamLoader { protected boolean setupStreamAndSendStatus() { Uri uri = Uri.parse(mUrl); if (uri == null) { - mHandler.error( + mLoadListener.error( EventHandler.FILE_NOT_FOUND_ERROR, mContext.getString( com.android.internal.R.string.httpErrorBadUrl) + @@ -78,18 +74,14 @@ class ContentLoader extends StreamLoader { try { mDataStream = mContext.getContentResolver().openInputStream(uri); - mHandler.status(1, 1, 200, "OK"); + mLoadListener.status(1, 1, 200, "OK"); } catch (java.io.FileNotFoundException ex) { - mHandler.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex)); - return false; - - } catch (java.io.IOException ex) { - mHandler.error(EventHandler.FILE_ERROR, errString(ex)); + mLoadListener.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex)); return false; } catch (RuntimeException ex) { // readExceptionWithFileNotFoundExceptionFromParcel in DatabaseUtils // can throw a serial of RuntimeException. Catch them all here. - mHandler.error(EventHandler.FILE_ERROR, errString(ex)); + mLoadListener.error(EventHandler.FILE_ERROR, errString(ex)); return false; } return true; @@ -103,16 +95,4 @@ class ContentLoader extends StreamLoader { // content can change, we don't want WebKit to cache it headers.setCacheControl("no-store, no-cache"); } - - /** - * Construct a ContentLoader and instruct it to start loading. - * - * @param url "content:" url pointing to content to be loaded - * @param loadListener LoadListener to pass the content to - */ - public static void requestUrl(String url, LoadListener loadListener) { - ContentLoader loader = new ContentLoader(url, loadListener); - loader.load(); - } - } diff --git a/core/java/android/webkit/DataLoader.java b/core/java/android/webkit/DataLoader.java index 2a68a5d..235dc5be 100644 --- a/core/java/android/webkit/DataLoader.java +++ b/core/java/android/webkit/DataLoader.java @@ -62,10 +62,10 @@ class DataLoader extends StreamLoader { @Override protected boolean setupStreamAndSendStatus() { if (mDataStream != null) { - mHandler.status(1, 1, 200, "OK"); + mLoadListener.status(1, 1, 200, "OK"); return true; } else { - mHandler.error(EventHandler.ERROR, + mLoadListener.error(EventHandler.ERROR, mContext.getString(R.string.httpError)); return false; } @@ -74,16 +74,4 @@ class DataLoader extends StreamLoader { @Override protected void buildHeaders(android.net.http.Headers h) { } - - /** - * Construct a DataLoader and instruct it to start loading. - * - * @param url data: URL string optionally containing a mimetype - * @param loadListener LoadListener to pass the content to - */ - public static void requestUrl(String url, LoadListener loadListener) { - DataLoader loader = new DataLoader(url, loadListener); - loader.load(); - } - } diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java index e856cde..e21e9ef 100644 --- a/core/java/android/webkit/FileLoader.java +++ b/core/java/android/webkit/FileLoader.java @@ -18,11 +18,9 @@ package android.webkit; import com.android.internal.R; -import android.content.Context; import android.content.res.AssetManager; import android.net.http.EventHandler; import android.net.http.Headers; -import android.os.Environment; import android.util.Log; import android.util.TypedValue; @@ -111,7 +109,7 @@ class FileLoader extends StreamLoader { // "<package>.R$drawable" if (mPath == null || mPath.length() == 0) { Log.e(LOGTAG, "Need a path to resolve the res file"); - mHandler.error(EventHandler.FILE_ERROR, mContext + mLoadListener.error(EventHandler.FILE_ERROR, mContext .getString(R.string.httpErrorFileNotFound)); return false; @@ -120,7 +118,7 @@ class FileLoader extends StreamLoader { int dot = mPath.indexOf('.', slash); if (slash == -1 || dot == -1) { Log.e(LOGTAG, "Incorrect res path: " + mPath); - mHandler.error(EventHandler.FILE_ERROR, mContext + mLoadListener.error(EventHandler.FILE_ERROR, mContext .getString(R.string.httpErrorFileNotFound)); return false; } @@ -157,13 +155,13 @@ class FileLoader extends StreamLoader { errorMsg = "Caught IllegalAccessException: " + e; } if (errorMsg != null) { - mHandler.error(EventHandler.FILE_ERROR, mContext + mLoadListener.error(EventHandler.FILE_ERROR, mContext .getString(R.string.httpErrorFileNotFound)); return false; } } else { if (!mAllowFileAccess) { - mHandler.error(EventHandler.FILE_ERROR, + mLoadListener.error(EventHandler.FILE_ERROR, mContext.getString(R.string.httpErrorFileNotFound)); return false; } @@ -171,14 +169,14 @@ class FileLoader extends StreamLoader { mDataStream = new FileInputStream(mPath); mContentLength = (new File(mPath)).length(); } - mHandler.status(1, 1, 200, "OK"); + mLoadListener.status(1, 1, 200, "OK"); } catch (java.io.FileNotFoundException ex) { - mHandler.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex)); + mLoadListener.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex)); return false; } catch (java.io.IOException ex) { - mHandler.error(EventHandler.FILE_ERROR, errString(ex)); + mLoadListener.error(EventHandler.FILE_ERROR, errString(ex)); return false; } return true; @@ -188,22 +186,4 @@ class FileLoader extends StreamLoader { protected void buildHeaders(Headers headers) { // do nothing. } - - - /** - * Construct a FileLoader and instruct it to start loading. - * - * @param url Full file url pointing to content to be loaded - * @param loadListener LoadListener to pass the content to - * @param asset true if url points to an asset. - * @param allowFileAccess true if this FileLoader can load files from the - * file system. - */ - public static void requestUrl(String url, LoadListener loadListener, - int type, boolean allowFileAccess) { - FileLoader loader = new FileLoader(url, loadListener, type, - allowFileAccess); - loader.load(); - } - } diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java index 58eca38..b13c405 100644 --- a/core/java/android/webkit/FrameLoader.java +++ b/core/java/android/webkit/FrameLoader.java @@ -141,24 +141,29 @@ class FrameLoader { return true; } if (URLUtil.isAssetUrl(url)) { - FileLoader.requestUrl(url, loadListener, FileLoader.TYPE_ASSET, - true); + // load asset in a separate thread as it involves IO + new FileLoader(url, loadListener, FileLoader.TYPE_ASSET, true) + .enqueue(); return true; } else if (URLUtil.isResourceUrl(url)) { - FileLoader.requestUrl(url, loadListener, FileLoader.TYPE_RES, - true); + // load resource in a separate thread as it involves IO + new FileLoader(url, loadListener, FileLoader.TYPE_RES, true) + .enqueue(); return true; } else if (URLUtil.isFileUrl(url)) { - FileLoader.requestUrl(url, loadListener, FileLoader.TYPE_FILE, - settings.getAllowFileAccess()); + // load file in a separate thread as it involves IO + new FileLoader(url, loadListener, FileLoader.TYPE_FILE, settings + .getAllowFileAccess()).enqueue(); return true; } else if (URLUtil.isContentUrl(url)) { // Send the raw url to the ContentLoader because it will do a - // permission check and the url has to match.. - ContentLoader.requestUrl(loadListener.url(), loadListener); + // permission check and the url has to match. + // load content in a separate thread as it involves IO + new ContentLoader(loadListener.url(), loadListener).enqueue(); return true; } else if (URLUtil.isDataUrl(url)) { - DataLoader.requestUrl(url, loadListener); + // load data in the current thread to reduce the latency + new DataLoader(url, loadListener).load(); return true; } else if (URLUtil.isAboutUrl(url)) { loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length()); diff --git a/core/java/android/webkit/StreamLoader.java b/core/java/android/webkit/StreamLoader.java index ce26268..4c32997 100644 --- a/core/java/android/webkit/StreamLoader.java +++ b/core/java/android/webkit/StreamLoader.java @@ -20,12 +20,13 @@ import android.content.Context; import android.net.http.EventHandler; import android.net.http.Headers; import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; import android.os.Message; import java.io.IOException; import java.io.InputStream; - /** * This abstract class is used for all content loaders that rely on streaming * content into the rendering engine loading framework. @@ -44,9 +45,7 @@ import java.io.InputStream; * that indicates the content should not be cached. * */ -abstract class StreamLoader extends Handler { - - public static final String NO_STORE = "no-store"; +abstract class StreamLoader implements Handler.Callback { private static final int MSG_STATUS = 100; // Send status to loader private static final int MSG_HEADERS = 101; // Send headers to loader @@ -54,11 +53,19 @@ abstract class StreamLoader extends Handler { private static final int MSG_END = 103; // Send endData to loader protected final Context mContext; - protected final LoadListener mHandler; // loader class + protected final LoadListener mLoadListener; // loader class protected InputStream mDataStream; // stream to read data from protected long mContentLength; // content length of data private byte [] mData; // buffer to pass data to loader with. + // Handler which will be initialized in the thread where load() is called. + private Handler mHandler; + + // Handler which will be used to load StreamLoader in a separate thread + private static StreamQueueHandler sStreamQueueHandler; + + private static final Object sStreamQueueLock = new Object(); + /** * Constructor. Although this class calls the LoadListener, it only calls * the EventHandler Interface methods. LoadListener concrete class is used @@ -67,13 +74,13 @@ abstract class StreamLoader extends Handler { * @param loadlistener The LoadListener to call with the data. */ StreamLoader(LoadListener loadlistener) { - mHandler = loadlistener; + mLoadListener = loadlistener; mContext = loadlistener.getContext(); } /** * This method is called when the derived class should setup mDataStream, - * and call mHandler.status() to indicate that the load can occur. If it + * and call mLoadListener.status() to indicate that the load can occur. If it * fails to setup, it should still call status() with the error code. * * @return true if stream was successfully setup @@ -89,15 +96,40 @@ abstract class StreamLoader extends Handler { */ abstract protected void buildHeaders(Headers headers); + /** + * Calling this method to load this StreamLoader in a separate + * "StreamLoadingThread". + */ + final void enqueue() { + synchronized (sStreamQueueLock) { + if (sStreamQueueHandler == null) { + HandlerThread thread = new HandlerThread( + StreamQueueHandler.THREAD_NAME, + android.os.Process.THREAD_PRIORITY_DEFAULT + + android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE); + thread.start(); + sStreamQueueHandler = new StreamQueueHandler(thread.getLooper()); + } + } + + sStreamQueueHandler.obtainMessage(StreamQueueHandler.MSG_ADD_LOADER, + this).sendToTarget(); + } /** * Calling this method starts the load of the content for this StreamLoader. - * This method simply posts a message to send the status and returns - * immediately. + * This method simply creates a Handler in the current thread and posts a + * message to send the status and returns immediately. */ - public void load() { - if (!mHandler.isSynchronous()) { - sendMessage(obtainMessage(MSG_STATUS)); + final void load() { + synchronized (this) { + if (mHandler == null) { + mHandler = new Handler(this); + } + } + + if (!mLoadListener.isSynchronous()) { + mHandler.sendEmptyMessage(MSG_STATUS); } else { // Load the stream synchronously. if (setupStreamAndSendStatus()) { @@ -105,23 +137,20 @@ abstract class StreamLoader extends Handler { // to pass data to the loader mData = new byte[8192]; sendHeaders(); - while (!sendData() && !mHandler.cancelled()); + while (!sendData() && !mLoadListener.cancelled()); closeStreamAndSendEndData(); - mHandler.loadSynchronousMessages(); + mLoadListener.loadSynchronousMessages(); } } } - /* (non-Javadoc) - * @see android.os.Handler#handleMessage(android.os.Message) - */ - public void handleMessage(Message msg) { - if (DebugFlags.STREAM_LOADER && mHandler.isSynchronous()) { + public boolean handleMessage(Message msg) { + if (mLoadListener.isSynchronous()) { throw new AssertionError(); } - if (mHandler.cancelled()) { + if (mLoadListener.cancelled()) { closeStreamAndSendEndData(); - return; + return true; } switch(msg.what) { case MSG_STATUS: @@ -129,27 +158,27 @@ abstract class StreamLoader extends Handler { // We were able to open the stream, create the array // to pass data to the loader mData = new byte[8192]; - sendMessage(obtainMessage(MSG_HEADERS)); + mHandler.sendEmptyMessage(MSG_HEADERS); } break; case MSG_HEADERS: sendHeaders(); - sendMessage(obtainMessage(MSG_DATA)); + mHandler.sendEmptyMessage(MSG_DATA); break; case MSG_DATA: if (sendData()) { - sendMessage(obtainMessage(MSG_END)); + mHandler.sendEmptyMessage(MSG_END); } else { - sendMessage(obtainMessage(MSG_DATA)); + mHandler.sendEmptyMessage(MSG_DATA); } break; case MSG_END: closeStreamAndSendEndData(); break; default: - super.handleMessage(msg); - break; + return false; } + return true; } /** @@ -161,7 +190,7 @@ abstract class StreamLoader extends Handler { headers.setContentLength(mContentLength); } buildHeaders(headers); - mHandler.headers(headers); + mLoadListener.headers(headers); } /** @@ -176,12 +205,11 @@ abstract class StreamLoader extends Handler { try { int amount = mDataStream.read(mData); if (amount > 0) { - mHandler.data(mData, amount); + mLoadListener.data(mData, amount); return false; } } catch (IOException ex) { - mHandler.error(EventHandler.FILE_ERROR, - ex.getMessage()); + mLoadListener.error(EventHandler.FILE_ERROR, ex.getMessage()); } } return true; @@ -198,7 +226,24 @@ abstract class StreamLoader extends Handler { // ignore. } } - mHandler.endData(); + mLoadListener.endData(); } + private static class StreamQueueHandler extends Handler { + private static final String THREAD_NAME = "StreamLoadingThread"; + + private static final int MSG_ADD_LOADER = 101; + + StreamQueueHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_ADD_LOADER) { + StreamLoader loader = (StreamLoader) msg.obj; + loader.load(); + } + } + } } diff --git a/core/java/com/android/internal/app/TetherActivity.java b/core/java/com/android/internal/app/TetherActivity.java new file mode 100644 index 0000000..2b93dbc --- /dev/null +++ b/core/java/com/android/internal/app/TetherActivity.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2007 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.android.internal.app; + +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.IMountService; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.widget.Toast; +import android.util.Log; + +/** + * This activity is shown to the user for him/her to connect/disconnect a Tether + * connection. It will display notification when a suitable connection is made + * to allow the tether to be setup. A second notification will be show when a + * tether is active, allowing the user to manage tethered connections. + */ +public class TetherActivity extends AlertActivity implements + DialogInterface.OnClickListener { + + private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1; + + /* Used to detect when the USB cable is unplugged, so we can call finish() */ + private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() == ConnectivityManager.ACTION_TETHER_STATE_CHANGED) { + handleTetherStateChanged(intent); + } + } + }; + + private boolean mWantTethering; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // determine if we advertise tethering or untethering + ConnectivityManager cm = + (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm.getTetheredIfaces().length > 0) { + mWantTethering = false; + } else if (cm.getTetherableIfaces().length > 0) { + mWantTethering = true; + } else { + finish(); + return; + } + + // Set up the "dialog" + if (mWantTethering == true) { + mAlertParams.mIconId = com.android.internal.R.drawable.ic_dialog_usb; + mAlertParams.mTitle = getString(com.android.internal.R.string.tether_title); + mAlertParams.mMessage = getString(com.android.internal.R.string.tether_message); + mAlertParams.mPositiveButtonText = + getString(com.android.internal.R.string.tether_button); + mAlertParams.mPositiveButtonListener = this; + mAlertParams.mNegativeButtonText = + getString(com.android.internal.R.string.tether_button_cancel); + mAlertParams.mNegativeButtonListener = this; + } else { + mAlertParams.mIconId = com.android.internal.R.drawable.ic_dialog_usb; + mAlertParams.mTitle = getString(com.android.internal.R.string.tether_stop_title); + mAlertParams.mMessage = getString(com.android.internal.R.string.tether_stop_message); + mAlertParams.mPositiveButtonText = + getString(com.android.internal.R.string.tether_stop_button); + mAlertParams.mPositiveButtonListener = this; + mAlertParams.mNegativeButtonText = + getString(com.android.internal.R.string.tether_stop_button_cancel); + mAlertParams.mNegativeButtonListener = this; + } + setupAlert(); + } + + @Override + protected void onResume() { + super.onResume(); + + registerReceiver(mTetherReceiver, new IntentFilter( + ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); + } + + @Override + protected void onPause() { + super.onPause(); + + unregisterReceiver(mTetherReceiver); + } + + /** + * {@inheritDoc} + */ + public void onClick(DialogInterface dialog, int which) { + + if (which == POSITIVE_BUTTON) { + ConnectivityManager connManager = + (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); + // start/stop tethering + if (mWantTethering) { + if (!connManager.tether("ppp0")) { + showTetheringError(); + } + } else { + if (!connManager.untether("ppp0")) { + showUnTetheringError(); + } + } + } + // No matter what, finish the activity + finish(); + } + + private void handleTetherStateChanged(Intent intent) { + finish(); + } + + private void showTetheringError() { + Toast.makeText(this, com.android.internal.R.string.tether_error_message, + Toast.LENGTH_LONG).show(); + } + + private void showUnTetheringError() { + Toast.makeText(this, com.android.internal.R.string.tether_stop_error_message, + Toast.LENGTH_LONG).show(); + } + +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 665088a..1406b66 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1239,6 +1239,10 @@ <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> + <activity android:name="com.android.internal.app.TetherActivity" + android:theme="@style/Theme.Dialog.Alert" + android:excludeFromRecents="true"> + </activity> <activity android:name="com.android.internal.app.UsbStorageActivity" android:excludeFromRecents="true"> </activity> diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_active.png b/core/res/res/drawable-hdpi/stat_sys_tether_active.png Binary files differnew file mode 100755 index 0000000..4c14c07 --- /dev/null +++ b/core/res/res/drawable-hdpi/stat_sys_tether_active.png diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_usb.png b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png Binary files differnew file mode 100755 index 0000000..4c14c07 --- /dev/null +++ b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png diff --git a/core/res/res/drawable-hdpi/usb_android.png b/core/res/res/drawable-hdpi/usb_android.png Binary files differindex 8153ec4..f6f899a 100644 --- a/core/res/res/drawable-hdpi/usb_android.png +++ b/core/res/res/drawable-hdpi/usb_android.png diff --git a/core/res/res/drawable-hdpi/usb_android_connected.png b/core/res/res/drawable-hdpi/usb_android_connected.png Binary files differindex 6449b7c..583ca00 100644 --- a/core/res/res/drawable-hdpi/usb_android_connected.png +++ b/core/res/res/drawable-hdpi/usb_android_connected.png diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_active.png b/core/res/res/drawable-mdpi/stat_sys_tether_active.png Binary files differnew file mode 100644 index 0000000..2d0da4c --- /dev/null +++ b/core/res/res/drawable-mdpi/stat_sys_tether_active.png diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_usb.png b/core/res/res/drawable-mdpi/stat_sys_tether_usb.png Binary files differnew file mode 100644 index 0000000..2d0da4c --- /dev/null +++ b/core/res/res/drawable-mdpi/stat_sys_tether_usb.png diff --git a/core/res/res/drawable-mdpi/usb_android.png b/core/res/res/drawable-mdpi/usb_android.png Binary files differnew file mode 100644 index 0000000..bf16083 --- /dev/null +++ b/core/res/res/drawable-mdpi/usb_android.png diff --git a/core/res/res/drawable-mdpi/usb_android_connected.png b/core/res/res/drawable-mdpi/usb_android_connected.png Binary files differnew file mode 100644 index 0000000..1d0486c --- /dev/null +++ b/core/res/res/drawable-mdpi/usb_android_connected.png diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 30d0da7..d1bfc68 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1078,7 +1078,13 @@ <string name="permlab_changeNetworkState">change network connectivity</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_changeNetworkState">Allows an application to change - the state network connectivity.</string> + the state of network connectivity.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_changeTetherState">change tethered connectivity</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the applicaiton to do this. --> + <string name="permdesc_changeTetherState">Allows an application to change + the state of tethered network connectivity.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_changeBackgroundDataSetting">change background data usage setting</string> @@ -2200,4 +2206,40 @@ Used by AccessibilityService to announce the purpose of the view. --> <string name="description_star">favorite</string> + + + <!-- Strings for Tethering dialogs --> + <!-- This is the label for the activity, and should never be visible to the user. --> + <!-- See TETHERING. TETHERING_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to Tether. This is the title. --> + <string name="tether_title">USB tethering available</string> + <!-- See TETHER. This is the message. --> + <string name="tether_message">Select \"Tether\" if you want to share your phone\'s data connection with your computer.</string> + <!-- See TETHER. This is the button text to Tether the computer with the phone. --> + <string name="tether_button">Tether</string> + <!-- See TETHER. This is the button text to ignore the plugging in of the phone.. --> + <string name="tether_button_cancel">Cancel</string> + <!-- See TETHER. If there was an error mounting, this is the text. --> + <string name="tether_error_message">There is a problem tethering.</string> + <!-- TETHER: When the user connects the phone to a computer, we show a notification asking if he wants to share his cellular network connection. This is the title --> + <string name="tether_available_notification_title">USB tethering available</string> + <!-- See USB_STORAGE. This is the message. --> + <string name="tether_available_notification_message">Select to tether your computer to your phone.</string> + <!-- TETHER_STOP: While TETHER is enabled, we show a notification dialog asking if he wants to stop. This is the title --> + <string name="tether_stop_notification_title">Untether</string> + <!-- See TETHER. This is the message. --> + <string name="tether_stop_notification_message">Select to untether your computer.</string> + + <!-- TETHER stop dialog strings --> + <!-- This is the label for the activity, and should never be visible to the user. --> + <!-- See TETHER_STOP. TETHER_STOP_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to stop tethering. This is the title. --> + <string name="tether_stop_title">Disconnect tethering</string> + <!-- See TETHER_STOP. This is the message. --> + <string name="tether_stop_message">You have been sharing your phone\'s cellular data connection with your computer. Select \"Disconnect\" to disconnect USB tethering.</string> + <!-- See TETHER_STOP. This is the button text to disconnect tethering. --> + <string name="tether_stop_button">Disconnect</string> + <!-- See TETHER_STOP. This is the button text to cancel disconnecting the tether. --> + <string name="tether_stop_button_cancel">Cancel</string> + <!-- See TETHER_STOP_DIALOG. If there was an error disconnect, this is the text. --> + <string name="tether_stop_error_message">We\'ve encountered a problem turning off Tethering. Please try again.</string> + </resources> diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index aa4956f..4259016 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -43,6 +43,8 @@ import android.util.Log; import com.android.internal.telephony.Phone; +import com.android.server.connectivity.Tethering; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -62,6 +64,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final String NETWORK_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore"; + + private Tethering mTethering; + /** * Sometimes we want to refer to the individual network state * trackers separately, and sometimes we just want to treat them @@ -308,6 +313,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { continue; } } + + mTethering = new Tethering(mContext); } @@ -784,6 +791,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { "ConnectivityService"); } + // TODO Make this a special check when it goes public + private void enforceTetherChangePermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CHANGE_NETWORK_STATE, + "ConnectivityService"); + } + /** * Handle a {@code DISCONNECTED} event. If this pertains to the non-active * network, we ignore it. If it is for the active network, we send out a @@ -1368,4 +1382,28 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } } + + // javadoc from interface + public boolean tether(String iface) { + enforceTetherChangePermission(); + return mTethering.tether(iface); + } + + // javadoc from interface + public boolean untether(String iface) { + enforceTetherChangePermission(); + return mTethering.untether(iface); + } + + // TODO - move iface listing, queries, etc to new module + // javadoc from interface + public String[] getTetherableIfaces() { + enforceAccessPermission(); + return mTethering.getTetherableIfaces(); + } + + public String[] getTetheredIfaces() { + enforceAccessPermission(); + return mTethering.getTetheredIfaces(); + } } diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index b34b50a..d41aacf 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -334,9 +334,9 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); try { - String cmd = "tether dns set "; + String cmd = "tether dns set"; for (String s : dns) { - cmd += InetAddress.getByName(s).toString() + " "; + cmd += " " + InetAddress.getByName(s).getHostAddress(); } mConnector.doCommand(cmd); } catch (UnknownHostException e) { @@ -373,14 +373,16 @@ class NetworkManagementService extends INetworkManagementService.Stub { return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult); } - public void attachPppd(String tty, String localAddr, String remoteAddr) - throws IllegalStateException { + public void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr, + String dns2Addr) throws IllegalStateException { try { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("pppd attach %s %s %s", tty, - InetAddress.getByName(localAddr).toString(), - InetAddress.getByName(localAddr).toString())); + mConnector.doCommand(String.format("pppd attach %s %s %s %s %s", tty, + InetAddress.getByName(localAddr).getHostAddress(), + InetAddress.getByName(remoteAddr).getHostAddress(), + InetAddress.getByName(dns1Addr).getHostAddress(), + InetAddress.getByName(dns2Addr).getHostAddress())); } catch (UnknownHostException e) { throw new IllegalStateException("Error resolving addr", e); } @@ -392,4 +394,3 @@ class NetworkManagementService extends INetworkManagementService.Stub { mConnector.doCommand(String.format("pppd detach %s", tty)); } } - diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java new file mode 100644 index 0000000..f685383 --- /dev/null +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2010 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.server.connectivity; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.INetworkManagementEventObserver; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Log; + +import java.util.ArrayList; +/** + * @hide + */ +public class Tethering extends INetworkManagementEventObserver.Stub { + + private Notification mTetheringNotification; + private Context mContext; + private final String TAG = "Tethering"; + + private boolean mPlaySounds = false; + + private ArrayList<String> mAvailableIfaces; + private ArrayList<String> mActiveIfaces; + + private ArrayList<String> mActiveTtys; + + private BroadcastReceiver mStateReceiver; + + public Tethering(Context context) { + Log.d(TAG, "Tethering starting"); + mContext = context; + + // register for notifications from NetworkManagement Service + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + try { + service.registerObserver(this); + } catch (RemoteException e) { + Log.e(TAG, "Error registering observer :" + e); + } + + mAvailableIfaces = new ArrayList<String>(); + mActiveIfaces = new ArrayList<String>(); + mActiveTtys = new ArrayList<String>(); + + // TODO - remove this hack after real USB connections are detected. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_UMS_DISCONNECTED); + filter.addAction(Intent.ACTION_UMS_CONNECTED); + mStateReceiver = new UMSStateReceiver(); + mContext.registerReceiver(mStateReceiver, filter); + } + + public synchronized void interfaceLinkStatusChanged(String iface, boolean link) { + Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link); + } + + public synchronized void interfaceAdded(String iface) { + if (mActiveIfaces.contains(iface)) { + Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring"); + return; + } + if (mAvailableIfaces.contains(iface)) { + Log.e(TAG, "available iface (" + iface + ") readded, ignoring"); + return; + } + mAvailableIfaces.add(iface); + Log.d(TAG, "interfaceAdded :" + iface); + sendTetherStateChangedBroadcast(); + } + + public synchronized void interfaceRemoved(String iface) { + if (mActiveIfaces.contains(iface)) { + Log.d(TAG, "removed an active iface (" + iface + ")"); + untether(iface); + } + if (mAvailableIfaces.contains(iface)) { + mAvailableIfaces.remove(iface); + Log.d(TAG, "interfaceRemoved " + iface); + sendTetherStateChangedBroadcast(); + } + } + + public synchronized boolean tether(String iface) { + Log.d(TAG, "Tethering " + iface); + + if (!mAvailableIfaces.contains(iface)) { + Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring"); + return false; + } + if (mActiveIfaces.contains(iface)) { + Log.e(TAG, "Tried to Tether an already Tethered iface :" + iface + ", ignoring"); + return false; + } + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + if (mActiveIfaces.size() == 0) { + try { + service.setIpForwardingEnabled(true); + } catch (Exception e) { + Log.e(TAG, "Error in setIpForwardingEnabled(true) :" + e); + return false; + } + + try { + // TODO - don't hardcode this - though with non-conf values (un-routable) + // maybe it's not a big deal + service.startTethering("169.254.2.1", "169.254.2.64"); + } catch (Exception e) { + Log.e(TAG, "Error in startTethering :" + e); + try { + service.setIpForwardingEnabled(false); + } catch (Exception ee) {} + return false; + } + + try { + // TODO - maybe use the current connection's dns servers for this + String[] dns = new String[2]; + dns[0] = new String("8.8.8.8"); + dns[1] = new String("4.2.2.2"); + service.setDnsForwarders(dns); + } catch (Exception e) { + Log.e(TAG, "Error in setDnsForwarders :" + e); + try { + service.stopTethering(); + } catch (Exception ee) {} + try { + service.setIpForwardingEnabled(false); + } catch (Exception ee) {} + } + } + + try { + service.tetherInterface(iface); + } catch (Exception e) { + Log.e(TAG, "Error in tetherInterface :" + e); + if (mActiveIfaces.size() == 0) { + try { + service.stopTethering(); + } catch (Exception ee) {} + try { + service.setIpForwardingEnabled(false); + } catch (Exception ee) {} + } + return false; + } + + try { + // TODO - use the currently active external iface + service.enableNat (iface, "rmnet0"); + } catch (Exception e) { + Log.e(TAG, "Error in enableNat :" + e); + try { + service.untetherInterface(iface); + } catch (Exception ee) {} + if (mActiveIfaces.size() == 0) { + try { + service.stopTethering(); + } catch (Exception ee) {} + try { + service.setIpForwardingEnabled(false); + } catch (Exception ee) {} + } + return false; + } + mAvailableIfaces.remove(iface); + mActiveIfaces.add(iface); + Log.d(TAG, "Tethered " + iface); + sendTetherStateChangedBroadcast(); + return true; + } + + public synchronized boolean untether(String iface) { + Log.d(TAG, "Untethering " + iface); + + if (mAvailableIfaces.contains(iface)) { + Log.e(TAG, "Tried to Untether an available iface :" + iface); + return false; + } + if (!mActiveIfaces.contains(iface)) { + Log.e(TAG, "Tried to Untether an inactive iface :" + iface); + return false; + } + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + // none of these errors are recoverable - ie, multiple calls won't help + // and the user can't do anything. Basically a reboot is required and probably + // the device is misconfigured or something bad has happend. + // Because of this, we should try to unroll as much state as we can. + try { + service.disableNat(iface, "rmnet0"); + } catch (Exception e) { + Log.e(TAG, "Error in disableNat :" + e); + } + try { + service.untetherInterface(iface); + } catch (Exception e) { + Log.e(TAG, "Error untethering " + iface + ", :" + e); + } + mActiveIfaces.remove(iface); + mAvailableIfaces.add(iface); + + if (mActiveIfaces.size() == 0) { + Log.d(TAG, "no active tethers - turning down dhcp/ipforward"); + try { + service.stopTethering(); + } catch (Exception e) { + Log.e(TAG, "Error in stopTethering :" + e); + } + try { + service.setIpForwardingEnabled(false); + } catch (Exception e) { + Log.e(TAG, "Error in setIpForwardingEnabled(false) :" + e); + } + } + sendTetherStateChangedBroadcast(); + Log.d(TAG, "Untethered " + iface); + return true; + } + + private void sendTetherStateChangedBroadcast() { + Intent broadcast = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); + broadcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + broadcast.putExtra(ConnectivityManager.EXTRA_AVAILABLE_TETHER_COUNT, + mAvailableIfaces.size()); + broadcast.putExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER_COUNT, mActiveIfaces.size()); + mContext.sendBroadcast(broadcast); + + // for USB we only have the one, so don't have to deal with additional + if (mAvailableIfaces.size() > 0) { + // Check if the user wants to be bothered + boolean tellUser = (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.TETHER_NOTIFY, 0) == 1); + + if (tellUser) { + showTetherAvailableNotification(); + } + } else if (mActiveIfaces.size() > 0) { + showTetheredNotification(); + } else { + clearNotification(); + } + } + + private void showTetherAvailableNotification() { + NotificationManager notificationManager = (NotificationManager)mContext. + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager == null) { + return; + } + + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.TetherActivity.class); + + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + + Resources r = Resources.getSystem(); + CharSequence title = r.getText(com.android.internal.R.string. + tether_available_notification_title); + CharSequence message = r.getText(com.android.internal.R.string. + tether_available_notification_message); + + if(mTetheringNotification == null) { + mTetheringNotification = new Notification(); + mTetheringNotification.when = 0; + } + mTetheringNotification.icon = com.android.internal.R.drawable.stat_sys_tether_usb; + + boolean playSounds = false; + //playSounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1"); + if (playSounds) { + mTetheringNotification.defaults |= Notification.DEFAULT_SOUND; + } else { + mTetheringNotification.defaults &= ~Notification.DEFAULT_SOUND; + } + + mTetheringNotification.flags = Notification.FLAG_ONGOING_EVENT; + mTetheringNotification.tickerText = title; + mTetheringNotification.setLatestEventInfo(mContext, title, message, pi); + + notificationManager.notify(mTetheringNotification.icon, mTetheringNotification); + + } + + private void showTetheredNotification() { + NotificationManager notificationManager = (NotificationManager)mContext. + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager == null) { + return; + } + + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.TetherActivity.class); + + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + + Resources r = Resources.getSystem(); + CharSequence title = r.getText(com.android.internal.R.string. + tether_stop_notification_title); + CharSequence message = r.getText(com.android.internal.R.string. + tether_stop_notification_message); + + if(mTetheringNotification == null) { + mTetheringNotification = new Notification(); + mTetheringNotification.when = 0; + } + mTetheringNotification.icon = com.android.internal.R.drawable.stat_sys_tether_usb; + + boolean playSounds = false; + //playSounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1"); + if (playSounds) { + mTetheringNotification.defaults |= Notification.DEFAULT_SOUND; + } else { + mTetheringNotification.defaults &= ~Notification.DEFAULT_SOUND; + } + + mTetheringNotification.flags = Notification.FLAG_ONGOING_EVENT; + mTetheringNotification.tickerText = title; + mTetheringNotification.setLatestEventInfo(mContext, title, message, pi); + + notificationManager.notify(mTetheringNotification.icon, mTetheringNotification); + } + + private void clearNotification() { + NotificationManager notificationManager = (NotificationManager)mContext. + getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null && mTetheringNotification != null) { + notificationManager.cancel(mTetheringNotification.icon); + mTetheringNotification = null; + } + } + + + + +// TODO - remove this hack after we get proper USB detection + private class UMSStateReceiver extends BroadcastReceiver { + public void onReceive(Context content, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_UMS_CONNECTED)) { + Tethering.this.handleTtyConnect(); + } else if (intent.getAction().equals(Intent.ACTION_UMS_DISCONNECTED)) { + Tethering.this.handleTtyDisconnect(); + } + } + } + + private synchronized void handleTtyConnect() { + Log.d(TAG, "handleTtyConnect"); + // for each of the available Tty not already supported by a ppp session, + // create a ppp session + // TODO - this should be data-driven rather than hard coded. + String[] allowedTtys = new String[1]; + allowedTtys[0] = new String("ttyGS0"); + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + String[] availableTtys; + try { + availableTtys = service.listTtys(); + } catch (RemoteException e) { + Log.e(TAG, "error listing Ttys :" + e); + return; + } + + for (String tty : availableTtys) { + for (String pattern : allowedTtys) { + if (tty.matches(pattern)) { + synchronized (this) { + if (!mActiveTtys.contains(tty)) { + // TODO - don't hardcode this + try { + // local, remote, dns + service.attachPppd(tty, "169.254.1.128", "169.254.1.1", + "169.254.1.128", "0.0.0.0"); + } catch (Exception e) { + Log.e(TAG, "error calling attachPppd: " + e); + return; + } + Log.d(TAG, "started Pppd on tty " + tty); + mActiveTtys.add(tty); + // TODO - remove this after we detect the new iface + interfaceAdded("ppp0"); + } + } + } + } + } + } + + private synchronized void handleTtyDisconnect() { + Log.d(TAG, "handleTtyDisconnect"); + + // TODO - this should be data-driven rather than hard coded. + String[] allowedTtys = new String[1]; + allowedTtys[0] = new String("ttyGS0"); + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + String[] availableTtys; + try { + availableTtys = service.listTtys(); + } catch (RemoteException e) { + Log.e(TAG, "error listing Ttys :" + e); + return; + } + + for (String tty : availableTtys) { + for (String pattern : allowedTtys) { + if (tty.matches(pattern)) { + synchronized (this) { + if (mActiveTtys.contains(tty)) { + try { + service.detachPppd(tty); + } catch (Exception e) { + Log.e(TAG, "error calling detachPppd on " + tty + " :" + e); + } + mActiveTtys.remove(tty); + // TODO - remove this after we detect the new iface + interfaceRemoved("ppp0"); + return; + } + } + } + } + } + } + + public synchronized String[] getTetheredIfaces() { + int size = mActiveIfaces.size(); + String[] result = new String[size]; + size -= 1; + for (int i=0; i< size; i++) { + result[i] = mActiveIfaces.get(i); + } + return result; + } + + public synchronized String[] getTetherableIfaces() { + int size = mAvailableIfaces.size(); + String[] result = new String[size]; + size -= 1; + for (int i=0; i< size; i++) { + result[i] = mActiveIfaces.get(i); + } + return result; + } +} diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java index e8a66c1..2667520 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java @@ -670,7 +670,12 @@ public class TestShellActivity extends Activity implements LayoutTestController public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, Message resultMsg) { if (!mCanOpenWindows) { - return false; + // We can't open windows, so just send null back. + WebView.WebViewTransport transport = + (WebView.WebViewTransport) resultMsg.obj; + transport.setWebView(null); + resultMsg.sendToTarget(); + return true; } // We never display the new window, just create the view and @@ -688,6 +693,11 @@ public class TestShellActivity extends Activity implements LayoutTestController resultMsg.sendToTarget(); return true; } + + @Override + public void onCloseWindow(WebView view) { + view.destroy(); + } }; private static class NewWindowWebView extends WebView { |