Merge "HIDD: Address API Review concerns"
diff --git a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothA2dpFacade.java b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothA2dpFacade.java
index 932bcbd..bc5f54c 100644
--- a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothA2dpFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothA2dpFacade.java
@@ -140,8 +140,8 @@
         if (sA2dpProfile == null) {
             return false;
         }
-        BluetoothDevice mDevice = BluetoothFacade.getDevice(
-                BluetoothFacade.DiscoveredDevices, deviceID);
+        BluetoothDevice mDevice =
+                BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), deviceID);
         Log.d("Connecting to device " + mDevice.getAliasName());
         return a2dpConnect(mDevice);
     }
diff --git a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHspFacade.java b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHspFacade.java
index 0af2b9c..5870674 100644
--- a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHspFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHspFacade.java
@@ -100,7 +100,8 @@
       throws Exception {
     if (sHspProfile == null)
       return false;
-    BluetoothDevice mDevice = BluetoothFacade.getDevice(BluetoothFacade.DiscoveredDevices, device);
+    BluetoothDevice mDevice =
+        BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), device);
     Log.d("Connecting to device " + mDevice.getAliasName());
     return hspConnect(mDevice);
   }
@@ -244,7 +245,71 @@
     return sHspProfile.isAudioConnected(device);
   }
 
+  /**
+   * Start voice recognition. Send BVRA command.
+   *
+   * @param deviceAddress the Bluetooth MAC address of remote device
+   * @return True if started successfully, False otherwise.
+   */
+  @Rpc(description = "Start Voice Recognition.")
+  public Boolean bluetoothHspStartVoiceRecognition(
+        @RpcParameter(name = "deviceAddress", description = "MAC address of a bluetooth device.")
+                  String deviceAddress) throws Exception {
+    BluetoothDevice device = BluetoothFacade.getDevice(sHspProfile.getConnectedDevices(), deviceAddress);
+    return sHspProfile.startVoiceRecognition(device);
+  }
+
+  /**
+   * Stop voice recognition. Send BVRA command.
+   *
+   * @param deviceAddress the Bluetooth MAC address of remote device
+   * @return True if stopped successfully, False otherwise.
+   */
+  @Rpc(description = "Stop Voice Recognition.")
+  public Boolean bluetoothHspStopVoiceRecognition(
+        @RpcParameter(name = "deviceAddress", description = "MAC address of a bluetooth device.")
+                  String deviceAddress) throws Exception {
+    BluetoothDevice device = BluetoothFacade.getDevice(sHspProfile.getConnectedDevices(), deviceAddress);
+    return sHspProfile.stopVoiceRecognition(device);
+  }
+
+  /**
+   * Determine whether in-band ringtone is enabled or not.
+   *
+   * @return True if enabled, False otherwise.
+   */
+  @Rpc(description = "In-band ringtone enabled check.")
+  public Boolean bluetoothHspIsInbandRingingEnabled() {
+    return sHspProfile.isInbandRingingEnabled();
+  }
+
+  /**
+   * Send a CLCC response from Sl4a (experimental).
+   *
+   * @param index the index of the call
+   * @param direction the direction of the call
+   * @param status the status of the call
+   * @param mode the mode
+   * @param mpty the mpty value
+   * @param number the phone number
+   * @param type the type
+   * @return True if stopped successfully, False otherwise.
+   */
+  @Rpc(description = "Send generic clcc response.")
+  public void bluetoothHspClccResponse(
+        @RpcParameter(name = "index", description = "") Integer index,
+        @RpcParameter(name = "direction", description = "") Integer direction,
+        @RpcParameter(name = "status", description = "") Integer status,
+        @RpcParameter(name = "mode", description = "") Integer mode,
+        @RpcParameter(name = "mpty", description = "") Boolean mpty,
+        @RpcParameter(name = "number", description = "") String number,
+        @RpcParameter(name = "type", description = "") Integer type
+                  ) {
+    sHspProfile.clccResponse(index, direction, status, mode, mpty, number, type);
+  }
+
   @Override
   public void shutdown() {
   }
 }
+
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/SmsFacade.java b/Common/src/com/googlecode/android_scripting/facade/telephony/SmsFacade.java
index 178f597..08c5cf0 100644
--- a/Common/src/com/googlecode/android_scripting/facade/telephony/SmsFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/SmsFacade.java
@@ -27,7 +27,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Telephony.Sms.Intents;
-import android.provider.Telephony.Mms;
 import android.telephony.SmsCbCmasInfo;
 import android.telephony.SmsCbEtwsInfo;
 import android.telephony.SmsCbMessage;
@@ -582,7 +581,7 @@
                 event.putString("Type", "NewWapPushReceived");
                 mEventFacade.postEvent(TelephonyConstants.EventWapPushReceived, event);
             }
-            if (Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) {
+            else if (Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) {
                 Log.d("New Data SMS Received");
                 Bundle event = new Bundle();
                 event.putString("Type", "NewDataSMSReceived");
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java
index 4efe857..88f6294 100644
--- a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java
@@ -347,6 +347,7 @@
     public static final String EventSignalStrengthChanged = "SignalStrengthChanged";
     public static final String EventVolteServiceStateChanged = "VolteServiceStateChanged";
     public static final String EventMessageWaitingIndicatorChanged = "MessageWaitingIndicatorChanged";
+    public static final String EventPhysicalChannelConfigChanged = "PhysicalChannelConfigChanged";
 
     /**
      * Constants for OnStartTetheringCallback
@@ -420,6 +421,9 @@
         public static final String SYSTEM_ID = "systemId";
         public static final String SUBSCRIPTION_ID = "subscriptionId";
         public static final String SERVICE_STATE = "serviceState";
+        public static final String CHANNEL_NUMBER = "channelNumber";
+        public static final String CELL_BANDWIDTHS = "cellBandwidths";
+        public static final String DUPLEX_MODE = "duplexMode";
     }
 
     public static class MessageWaitingIndicatorContainer {
@@ -429,4 +433,10 @@
     public static class VoLteServiceStateContainer {
         public static final String SRVCC_STATE = "srvccState";
     }
+
+    public static class PhysicalChannelConfigContainer {
+        public static final String CONFIGS = "configs";
+        public static final String CELL_BANDWIDTH_DOWNLINK = "cellBandwidthDownlink";
+        public static final String CONNECTION_STATUS  = "cellConnectionStatus";
+    }
 }
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyEvents.java b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyEvents.java
index d4e234e..982d9ef 100644
--- a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyEvents.java
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyEvents.java
@@ -16,13 +16,19 @@
 
 package com.googlecode.android_scripting.facade.telephony;
 
-import org.json.JSONException;
-import org.json.JSONObject;
 import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
 import android.telephony.ServiceState;
+
 import com.googlecode.android_scripting.jsonrpc.JsonSerializable;
 
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
 public class TelephonyEvents {
 
     public static class CallStateEvent implements JsonSerializable {
@@ -266,13 +272,24 @@
                     mServiceState.isEmergencyOnly());
             serviceState.put(
                     TelephonyConstants.ServiceStateContainer.NETWORK_ID,
-                    mServiceState.getNetworkId());
+                    mServiceState.getCdmaNetworkId());
             serviceState.put(
                     TelephonyConstants.ServiceStateContainer.SYSTEM_ID,
-                    mServiceState.getSystemId());
+                    mServiceState.getCdmaSystemId());
             serviceState.put(
                     TelephonyConstants.ServiceStateContainer.SERVICE_STATE,
                     mServiceStateString);
+            serviceState.put(
+                    TelephonyConstants.ServiceStateContainer.CHANNEL_NUMBER,
+                    mServiceState.getChannelNumber());
+            serviceState.put(
+                    TelephonyConstants.ServiceStateContainer.CELL_BANDWIDTHS,
+                    mServiceState.getCellBandwidths() != null
+                            ? new JSONArray(mServiceState.getCellBandwidths())
+                            : JSONObject.NULL);
+            serviceState.put(
+                    TelephonyConstants.ServiceStateContainer.DUPLEX_MODE,
+                    mServiceState.getDuplexMode());
 
             return serviceState;
         }
@@ -299,4 +316,32 @@
             return messageWaitingIndicator;
         }
     }
+
+    public static class PhysicalChannelConfigChangedEvent implements JsonSerializable {
+        private final List<PhysicalChannelConfig> mConfigs;
+
+        PhysicalChannelConfigChangedEvent(List<PhysicalChannelConfig> configs) {
+            mConfigs = configs;
+        }
+
+        List<PhysicalChannelConfig> getConfigs() {
+            return mConfigs;
+        }
+
+        public JSONObject toJSON() throws JSONException {
+            JSONArray jsonConfigs = new JSONArray();
+            for(PhysicalChannelConfig c : mConfigs) {
+                JSONObject cfg  = new JSONObject();
+                cfg.put(
+                        TelephonyConstants.PhysicalChannelConfigContainer.CELL_BANDWIDTH_DOWNLINK,
+                        c.getCellBandwidthDownlink());
+                cfg.put(
+                        TelephonyConstants.PhysicalChannelConfigContainer.CONNECTION_STATUS,
+                        c.getConnectionStatus());
+               jsonConfigs.put(cfg);
+            }
+            return new JSONObject().put(
+                    TelephonyConstants.PhysicalChannelConfigContainer.CONFIGS, jsonConfigs);
+        }
+    }
 }
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java
index 325a354..0f7d5b1 100644
--- a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java
@@ -582,6 +582,24 @@
         return mTelephonyManager.getCellLocation();
     }
 
+    /**
+     *  Returns carrier id of the current subscription.
+     * @return Carrier id of the current subscription.
+     */
+    @Rpc(description = "Returns the numeric CarrierId for current subscription")
+    public int telephonyGetSimCarrierId() {
+        return mTelephonyManager.getSimCarrierId();
+    }
+
+    /**
+     *  Returns carrier id name of the current subscription.
+     * @return Carrier id name of the current subscription
+     */
+    @Rpc(description = "Returns Carrier Name for current subscription")
+    public CharSequence telephonyGetSimCarrierIdName() {
+        return mTelephonyManager.getSimCarrierIdName();
+    }
+
     @Rpc(description = "Returns the numeric name (MCC+MNC) of registered operator." +
                        "for default subscription ID")
     public String telephonyGetNetworkOperator() {
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyStateListeners.java b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyStateListeners.java
index f4d692a..b9fe705 100644
--- a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyStateListeners.java
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyStateListeners.java
@@ -21,6 +21,7 @@
 import android.telephony.CellInfo;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.PhoneStateListener;
+import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -328,4 +329,33 @@
         }
     }
 
+    public static class PhysicalChannelConfigurationChangeListener extends PhoneStateListener {
+
+        private final EventFacade mEventFacade;
+        public List<PhysicalChannelConfig> mConfigs;
+        public static final int sListeningStates =
+                PhoneStateListener.LISTEN_PHYSICAL_CHANNEL_CONFIGURATION;
+        public PhysicalChannelConfigurationChangeListener(EventFacade ef) {
+            super();
+            mEventFacade = ef;
+        }
+
+        public PhysicalChannelConfigurationChangeListener(EventFacade ef, int subId) {
+            super(subId);
+            mEventFacade = ef;
+        }
+
+        public PhysicalChannelConfigurationChangeListener(
+                EventFacade ef, int subId, Looper looper) {
+            super(subId, looper);
+            mEventFacade = ef;
+        }
+
+        @Override
+        public void onPhysicalChannelConfigurationChanged(List<PhysicalChannelConfig> config) {
+            mEventFacade.postEvent(
+                TelephonyConstants.EventPhysicalChannelConfigChanged, config);
+        }
+    }
+
 }
diff --git a/Common/src/com/googlecode/android_scripting/webcam/JpegProvider.java b/Common/src/com/googlecode/android_scripting/facade/webcam/JpegProvider.java
similarity index 88%
rename from Common/src/com/googlecode/android_scripting/webcam/JpegProvider.java
rename to Common/src/com/googlecode/android_scripting/facade/webcam/JpegProvider.java
index 883af81..6f73371 100644
--- a/Common/src/com/googlecode/android_scripting/webcam/JpegProvider.java
+++ b/Common/src/com/googlecode/android_scripting/facade/webcam/JpegProvider.java
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-package com.googlecode.android_scripting.webcam;
+package com.googlecode.android_scripting.facade.webcam;
 
 interface JpegProvider {
-  public byte[] getJpeg();
-}
\ No newline at end of file
+    byte[] getJpeg();
+}
diff --git a/Common/src/com/googlecode/android_scripting/facade/webcam/MjpegServer.java b/Common/src/com/googlecode/android_scripting/facade/webcam/MjpegServer.java
new file mode 100644
index 0000000..af3d9e6
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/facade/webcam/MjpegServer.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.googlecode.android_scripting.facade.webcam;
+
+import com.googlecode.android_scripting.Log;
+import com.googlecode.android_scripting.SimpleServer;
+
+import java.io.BufferedReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.Socket;
+
+class MjpegServer extends SimpleServer {
+
+    private final JpegProvider mProvider;
+
+    MjpegServer(JpegProvider provider) {
+        mProvider = provider;
+    }
+
+    @Override
+    protected void handleConnection(Socket socket) throws Exception {
+        Log.d("handle Mjpeg connection");
+        byte[] data = mProvider.getJpeg();
+        if (data == null) {
+            return;
+        }
+        OutputStream outputStream = socket.getOutputStream();
+        outputStream.write((
+                "HTTP/1.0 200 OK\r\n"
+                        + "Server: SL4A\r\n"
+                        + "Connection: close\r\n"
+                        + "Max-Age: 0\r\n"
+                        + "Expires: 0\r\n"
+                        + "Cache-Control: no-cache, private\r\n"
+                        + "Pragma: no-cache\r\n"
+                        + "Content-Type: multipart/x-mixed-replace; "
+                        + "boundary=--BoundaryString\r\n\r\n").getBytes());
+        while (true) {
+            data = mProvider.getJpeg();
+            if (data == null) {
+                return;
+            }
+            outputStream.write("--BoundaryString\r\n".getBytes());
+            outputStream.write("Content-type: image/jpg\r\n".getBytes());
+            outputStream.write(("Content-Length: " + data.length + "\r\n\r\n").getBytes());
+            outputStream.write(data);
+            outputStream.write("\r\n\r\n".getBytes());
+            outputStream.flush();
+        }
+    }
+
+    @Override
+    protected void handleRPCConnection(Socket sock,
+                                       Integer uid,
+                                       BufferedReader reader,
+                                       PrintWriter writer)
+            throws Exception {
+    }
+}
diff --git a/Common/src/com/googlecode/android_scripting/facade/webcam/WebCamFacade.java b/Common/src/com/googlecode/android_scripting/facade/webcam/WebCamFacade.java
new file mode 100644
index 0000000..bc7bbbf
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/facade/webcam/WebCamFacade.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2017 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.googlecode.android_scripting.facade.webcam;
+
+import android.app.Service;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PreviewCallback;
+import android.hardware.Camera.Size;
+import android.util.Base64;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.view.WindowManager;
+
+import com.googlecode.android_scripting.BaseApplication;
+import com.googlecode.android_scripting.FutureActivityTaskExecutor;
+import com.googlecode.android_scripting.Log;
+import com.googlecode.android_scripting.SimpleServer.SimpleServerObserver;
+import com.googlecode.android_scripting.SingleThreadExecutor;
+import com.googlecode.android_scripting.facade.EventFacade;
+import com.googlecode.android_scripting.facade.FacadeManager;
+import com.googlecode.android_scripting.future.FutureActivityTask;
+import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
+import com.googlecode.android_scripting.rpc.Rpc;
+import com.googlecode.android_scripting.rpc.RpcDefault;
+import com.googlecode.android_scripting.rpc.RpcOptional;
+import com.googlecode.android_scripting.rpc.RpcParameter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+/**
+ * Manages access to camera streaming.
+ * <br>
+ * <h3>Usage Notes</h3>
+ * <br><b>webCamStart</b> and <b>webCamStop</b> are used to start and stop an Mpeg stream on a
+ * given port. <b>webcamAdjustQuality</b> is used to ajust the quality of the streaming video.
+ * <br><b>cameraStartPreview</b> is used to get access to the camera preview screen. It will
+ * generate "preview" events as images become available.
+ * <br>The preview has two modes: data or file. If you pass a non-blank, writable file path to
+ * the <b>cameraStartPreview</b> it will store jpg images in that folder.
+ * It is up to the caller to clean up these files after the fact. If no file element is provided,
+ * the event will include the image data as a base64 encoded string.
+ * <h3>Event details</h3>
+ * <br>The data element of the preview event will be a map, with the following elements defined.
+ * <ul>
+ * <li><b>format</b> - currently always "jpeg"
+ * <li><b>width</b> - image width (in pixels)
+ * <li><b>height</b> - image height (in pixels)
+ * <li><b>quality</b> - JPEG quality. Number from 1-100
+ * <li><b>filename</b> - Name of file where image was saved. Only relevant if filepath defined.
+ * <li><b>error</b> - included if there was an IOException saving file, ie, disk full or path write
+ * protected.
+ * <li><b>encoding</b> - Data encoding. If filepath defined, will be "file" otherwise "base64"
+ * <li><b>data</b> - Base64 encoded image data.
+ * </ul>
+ * <br>Note that "filename", "error" and "data" are mutual exclusive.
+ * <br>
+ * <br>The webcam and preview modes use the same resources, so you can't use them both at the same
+ * time. Stop one mode before starting the other.
+ */
+public class WebCamFacade extends RpcReceiver {
+
+    private final Service mService;
+    private final Executor mJpegCompressionExecutor = new SingleThreadExecutor();
+    private final ByteArrayOutputStream mJpegCompressionBuffer = new ByteArrayOutputStream();
+
+    private volatile byte[] mJpegData;
+
+    private CountDownLatch mJpegDataReady;
+    private boolean mStreaming;
+    private int mPreviewHeight;
+    private int mPreviewWidth;
+    private int mJpegQuality;
+
+    private MjpegServer mJpegServer;
+    private FutureActivityTask<SurfaceHolder> mPreviewTask;
+    private Camera mCamera;
+    private Parameters mParameters;
+    private final EventFacade mEventFacade;
+    private boolean mPreview;
+    private File mDest;
+
+    private final PreviewCallback mPreviewCallback = new PreviewCallback() {
+        @Override
+        public void onPreviewFrame(final byte[] data, final Camera camera) {
+            mJpegCompressionExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mJpegData = compressYuvToJpeg(data);
+                    mJpegDataReady.countDown();
+                    if (mStreaming) {
+                        camera.setOneShotPreviewCallback(mPreviewCallback);
+                    }
+                }
+            });
+        }
+    };
+
+    private final PreviewCallback mPreviewEvent = new PreviewCallback() {
+        @Override
+        public void onPreviewFrame(final byte[] data, final Camera camera) {
+            mJpegCompressionExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mJpegData = compressYuvToJpeg(data);
+                    Map<String, Object> map = new HashMap<String, Object>();
+                    map.put("format", "jpeg");
+                    map.put("width", mPreviewWidth);
+                    map.put("height", mPreviewHeight);
+                    map.put("quality", mJpegQuality);
+                    if (mDest != null) {
+                        try {
+                            File dest = File.createTempFile("prv", ".jpg", mDest);
+                            OutputStream output = new FileOutputStream(dest);
+                            output.write(mJpegData);
+                            output.close();
+                            map.put("encoding", "file");
+                            map.put("filename", dest.toString());
+                        } catch (IOException e) {
+                            map.put("error", e.toString());
+                        }
+                    } else {
+                        map.put("encoding", "Base64");
+                        map.put("data", Base64.encodeToString(mJpegData, Base64.DEFAULT));
+                    }
+                    mEventFacade.postEvent("preview", map);
+                    if (mPreview) {
+                        camera.setOneShotPreviewCallback(mPreviewEvent);
+                    }
+                }
+            });
+        }
+    };
+
+    public WebCamFacade(FacadeManager manager) {
+        super(manager);
+        mService = manager.getService();
+        mJpegDataReady = new CountDownLatch(1);
+        mEventFacade = manager.getReceiver(EventFacade.class);
+    }
+
+    private byte[] compressYuvToJpeg(final byte[] yuvData) {
+        mJpegCompressionBuffer.reset();
+        YuvImage yuvImage =
+                new YuvImage(yuvData, ImageFormat.NV21, mPreviewWidth, mPreviewHeight, null);
+        yuvImage.compressToJpeg(new Rect(0, 0, mPreviewWidth, mPreviewHeight), mJpegQuality,
+                mJpegCompressionBuffer);
+        return mJpegCompressionBuffer.toByteArray();
+    }
+
+    /**
+     * Starts an MJPEG stream and returns a Tuple of address and port for the stream.
+     * @param resolutionLevel increasing this number provides higher resolution
+     * @param jpegQuality a number from 0-100
+     * @param port If port is specified, the webcam service will bind to port, otherwise it will
+     *             pick any available port.
+     * @return a Tuple of address and port for the stream
+     * @throws Exception upon failure to open the webcam or start the server
+     */
+    @Rpc(description = "Starts an MJPEG stream and returns a Tuple of address and port "
+            + "for the stream.")
+    public InetSocketAddress webcamStart(
+            @RpcParameter(name = "resolutionLevel",
+                          description = "increasing this number provides higher resolution")
+            @RpcDefault("0")
+                    Integer resolutionLevel,
+            @RpcParameter(name = "jpegQuality", description = "a number from 0-100")
+            @RpcDefault("20")
+                    Integer jpegQuality,
+            @RpcParameter(name = "port",
+                          description = "If port is specified, the webcam service will bind to "
+                                  + "port, otherwise it will pick any available port.")
+            @RpcDefault("0")
+                    Integer port)
+            throws Exception {
+        try {
+            openCamera(resolutionLevel, jpegQuality);
+            return startServer(port);
+        } catch (Exception e) {
+            webcamStop();
+            throw e;
+        }
+    }
+
+    private InetSocketAddress startServer(Integer port) {
+        mJpegServer = new MjpegServer(new JpegProvider() {
+            @Override
+            public byte[] getJpeg() {
+                try {
+                    mJpegDataReady.await();
+                } catch (InterruptedException e) {
+                    Log.e(e);
+                }
+                return mJpegData;
+            }
+        });
+        mJpegServer.addObserver(new SimpleServerObserver() {
+            @Override
+            public void onDisconnect() {
+                if (mJpegServer.getNumberOfConnections() == 0 && mStreaming) {
+                    stopStream();
+                }
+            }
+
+            @Override
+            public void onConnect() {
+                if (!mStreaming) {
+                    startStream();
+                }
+            }
+        });
+        return mJpegServer.startPublic(port);
+    }
+
+    private void stopServer() {
+        if (mJpegServer != null) {
+            mJpegServer.shutdown();
+            mJpegServer = null;
+        }
+    }
+
+    /**
+     * Adjusts the quality of the webcam stream while it is running.
+     * @param resolutionLevel increasing this number provides higher resolution
+     * @param jpegQuality a number from 0-100
+     * @throws Exception if the webcam is not streaming or the camera is unable to open
+     */
+    @Rpc(description = "Adjusts the quality of the webcam stream while it is running.")
+    public void webcamAdjustQuality(
+            @RpcParameter(name = "resolutionLevel",
+                          description = "increasing this number provides higher resolution")
+            @RpcDefault("0")
+                    Integer resolutionLevel,
+            @RpcParameter(name = "jpegQuality", description = "a number from 0-100")
+            @RpcDefault("20")
+                    Integer jpegQuality)
+            throws Exception {
+        if (!mStreaming) {
+            throw new IllegalStateException("Webcam not streaming.");
+        }
+        stopStream();
+        releaseCamera();
+        openCamera(resolutionLevel, jpegQuality);
+        startStream();
+    }
+
+    private void openCamera(Integer resolutionLevel, Integer jpegQuality) throws IOException,
+            InterruptedException {
+        mCamera = Camera.open();
+        mParameters = mCamera.getParameters();
+        mParameters.setPictureFormat(ImageFormat.JPEG);
+        mParameters.setPreviewFormat(ImageFormat.JPEG);
+        List<Size> supportedPreviewSizes = mParameters.getSupportedPreviewSizes();
+        Collections.sort(supportedPreviewSizes, new Comparator<Size>() {
+            @Override
+            public int compare(Size o1, Size o2) {
+                return o1.width - o2.width;
+            }
+        });
+        Size previewSize =
+                supportedPreviewSizes.get(
+                        Math.min(resolutionLevel, supportedPreviewSizes.size() - 1));
+        mPreviewHeight = previewSize.height;
+        mPreviewWidth = previewSize.width;
+        mParameters.setPreviewSize(mPreviewWidth, mPreviewHeight);
+        mJpegQuality = Math.min(Math.max(jpegQuality, 0), 100);
+        mCamera.setParameters(mParameters);
+        // TODO(damonkohler): Rotate image based on orientation.
+        mPreviewTask = createPreviewTask();
+        mCamera.startPreview();
+    }
+
+    private void startStream() {
+        mStreaming = true;
+        mCamera.setOneShotPreviewCallback(mPreviewCallback);
+    }
+
+    private void stopStream() {
+        mJpegDataReady = new CountDownLatch(1);
+        mStreaming = false;
+        if (mPreviewTask != null) {
+            mPreviewTask.finish();
+            mPreviewTask = null;
+        }
+    }
+
+    private void releaseCamera() {
+        if (mCamera != null) {
+            mCamera.release();
+            mCamera = null;
+        }
+        mParameters = null;
+    }
+
+    /** Stops the webcam stream. */
+    @Rpc(description = "Stops the webcam stream.")
+    public void webcamStop() {
+        stopServer();
+        stopStream();
+        releaseCamera();
+    }
+
+    private FutureActivityTask<SurfaceHolder> createPreviewTask() throws IOException,
+            InterruptedException {
+        FutureActivityTask<SurfaceHolder> task = new FutureActivityTask<SurfaceHolder>() {
+            @Override
+            public void onCreate() {
+                super.onCreate();
+                final SurfaceView view = new SurfaceView(getActivity());
+                getActivity().setContentView(view);
+                getActivity().getWindow().setSoftInputMode(
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
+                //view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+                view.getHolder().addCallback(new Callback() {
+                    @Override
+                    public void surfaceDestroyed(SurfaceHolder holder) {
+                    }
+
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        setResult(view.getHolder());
+                    }
+
+                    @Override
+                    public void surfaceChanged(SurfaceHolder holder,
+                                               int format,
+                                               int width,
+                                               int height) {
+                    }
+                });
+            }
+        };
+        FutureActivityTaskExecutor taskExecutor =
+                ((BaseApplication) mService.getApplication()).getTaskExecutor();
+        taskExecutor.execute(task);
+        mCamera.setPreviewDisplay(task.getResult());
+        return task;
+    }
+
+    /**
+     * Start Preview Mode. Throws 'preview' events.
+     * @param resolutionLevel increasing this number provides higher resolution
+     * @param jpegQuality a number from 0-100
+     * @param filepath the path to store jpeg files
+     * @return a Tuple of address and port for the stream
+     * @throws InterruptedException if interrupted while trying to open the camera
+     */
+    @Rpc(description = "Start Preview Mode. Throws 'preview' events.",
+            returns = "True if successful")
+    public boolean cameraStartPreview(
+            @RpcParameter(name = "resolutionLevel",
+                          description = "increasing this number provides higher resolution")
+            @RpcDefault("0")
+                    Integer resolutionLevel,
+            @RpcParameter(name = "jpegQuality", description = "a number from 0-100")
+            @RpcDefault("20")
+                    Integer jpegQuality,
+            @RpcParameter(name = "filepath", description = "Path to store jpeg files.")
+            @RpcOptional
+                    String filepath)
+            throws InterruptedException {
+        mDest = null;
+        if (filepath != null && (filepath.length() > 0)) {
+            mDest = new File(filepath);
+            if (!mDest.exists()) mDest.mkdirs();
+            if (!(mDest.isDirectory() && mDest.canWrite())) {
+                return false;
+            }
+        }
+
+        try {
+            openCamera(resolutionLevel, jpegQuality);
+        } catch (IOException e) {
+            Log.e(e);
+            return false;
+        }
+        startPreview();
+        return true;
+    }
+
+    /** Stops the preview mode. */
+    @Rpc(description = "Stop the preview mode.")
+    public void cameraStopPreview() {
+        stopPreview();
+    }
+
+    private void startPreview() {
+        mPreview = true;
+        mCamera.setOneShotPreviewCallback(mPreviewEvent);
+    }
+
+    private void stopPreview() {
+        mPreview = false;
+        if (mPreviewTask != null) {
+            mPreviewTask.finish();
+            mPreviewTask = null;
+        }
+        releaseCamera();
+    }
+
+    @Override
+    public void shutdown() {
+        mPreview = false;
+        webcamStop();
+    }
+}
diff --git a/Common/src/com/googlecode/android_scripting/facade/wifi/HttpFacade.java b/Common/src/com/googlecode/android_scripting/facade/wifi/HttpFacade.java
index f8fbd20..6f173b5 100644
--- a/Common/src/com/googlecode/android_scripting/facade/wifi/HttpFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/wifi/HttpFacade.java
@@ -100,7 +100,7 @@
      */
     private HttpURLConnection httpRequest(String url) throws IOException {
         URL targetURL = new URL(url);
-        HttpURLConnection urlConnection = null;
+        HttpURLConnection urlConnection;
         try {
             urlConnection = (HttpURLConnection) targetURL.openConnection();
             urlConnection.connect();
@@ -110,6 +110,7 @@
         } catch (IOException e) {
             Log.e("Failed to open a connection to " + url);
             Log.e(e.toString());
+            throw e;
         }
         return urlConnection;
     }
diff --git a/Common/src/com/googlecode/android_scripting/webcam/MjpegServer.java b/Common/src/com/googlecode/android_scripting/webcam/MjpegServer.java
deleted file mode 100644
index 690f0dd..0000000
--- a/Common/src/com/googlecode/android_scripting/webcam/MjpegServer.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2017 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.googlecode.android_scripting.webcam;
-
-import java.io.BufferedReader;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.net.Socket;
-import com.googlecode.android_scripting.Log;
-import com.googlecode.android_scripting.SimpleServer;
-
-class MjpegServer extends SimpleServer {
-
-  private final JpegProvider mProvider;
-
-  public MjpegServer(JpegProvider provider) {
-    mProvider = provider;
-  }
-
-  @Override
-  protected void handleConnection(Socket socket) throws Exception {
-    Log.d("handle Mjpeg connection");
-    byte[] data = mProvider.getJpeg();
-    if (data == null) {
-      return;
-    }
-    OutputStream outputStream = socket.getOutputStream();
-    outputStream.write((
-        "HTTP/1.0 200 OK\r\n" +
-        "Server: SL4A\r\n" +
-        "Connection: close\r\n" +
-        "Max-Age: 0\r\n" +
-        "Expires: 0\r\n" +
-        "Cache-Control: no-cache, private\r\n" +
-        "Pragma: no-cache\r\n" +
-        "Content-Type: multipart/x-mixed-replace; boundary=--BoundaryString\r\n\r\n").getBytes());
-    while (true) {
-      data = mProvider.getJpeg();
-      if (data == null) {
-        return;
-      }
-      outputStream.write("--BoundaryString\r\n".getBytes());
-      outputStream.write("Content-type: image/jpg\r\n".getBytes());
-      outputStream.write(("Content-Length: " + data.length + "\r\n\r\n").getBytes());
-      outputStream.write(data);
-      outputStream.write("\r\n\r\n".getBytes());
-      outputStream.flush();
-    }
-  }
-
-  @Override
-  protected void handleRPCConnection(Socket sock, Integer UID, BufferedReader reader, PrintWriter writer)
-      throws Exception {
-  }
-}
diff --git a/Common/src/com/googlecode/android_scripting/webcam/WebCamFacade.java b/Common/src/com/googlecode/android_scripting/webcam/WebCamFacade.java
deleted file mode 100644
index a61681a..0000000
--- a/Common/src/com/googlecode/android_scripting/webcam/WebCamFacade.java
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * Copyright (C) 2017 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.googlecode.android_scripting.webcam;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-
-import android.app.Service;
-import android.graphics.ImageFormat;
-import android.graphics.Rect;
-import android.graphics.YuvImage;
-import android.hardware.Camera;
-import android.hardware.Camera.Parameters;
-import android.hardware.Camera.PreviewCallback;
-import android.hardware.Camera.Size;
-import android.util.Base64;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.WindowManager;
-import android.view.SurfaceHolder.Callback;
-
-import com.googlecode.android_scripting.BaseApplication;
-import com.googlecode.android_scripting.FutureActivityTaskExecutor;
-import com.googlecode.android_scripting.Log;
-import com.googlecode.android_scripting.SingleThreadExecutor;
-import com.googlecode.android_scripting.SimpleServer.SimpleServerObserver;
-import com.googlecode.android_scripting.facade.EventFacade;
-import com.googlecode.android_scripting.facade.FacadeManager;
-import com.googlecode.android_scripting.future.FutureActivityTask;
-import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
-import com.googlecode.android_scripting.rpc.Rpc;
-import com.googlecode.android_scripting.rpc.RpcDefault;
-import com.googlecode.android_scripting.rpc.RpcOptional;
-import com.googlecode.android_scripting.rpc.RpcParameter;
-
-/**
- * Manages access to camera streaming.
- * <br>
- * <h3>Usage Notes</h3>
- * <br><b>webCamStart</b> and <b>webCamStop</b> are used to start and stop an Mpeg stream on a given port. <b>webcamAdjustQuality</b> is used to ajust the quality of the streaming video.
- * <br><b>cameraStartPreview</b> is used to get access to the camera preview screen. It will generate "preview" events as images become available.
- * <br>The preview has two modes: data or file. If you pass a non-blank, writable file path to the <b>cameraStartPreview</b> it will store jpg images in that folder.
- * It is up to the caller to clean up these files after the fact. If no file element is provided,
- * the event will include the image data as a base64 encoded string.
- * <h3>Event details</h3>
- * <br>The data element of the preview event will be a map, with the following elements defined.
- * <ul>
- * <li><b>format</b> - currently always "jpeg"
- * <li><b>width</b> - image width (in pixels)
- * <li><b>height</b> - image height (in pixels)
- * <li><b>quality</b> - JPEG quality. Number from 1-100
- * <li><b>filename</b> - Name of file where image was saved. Only relevant if filepath defined.
- * <li><b>error</b> - included if there was an IOException saving file, ie, disk full or path write protected.
- * <li><b>encoding</b> - Data encoding. If filepath defined, will be "file" otherwise "base64"
- * <li><b>data</b> - Base64 encoded image data.
- * </ul>
- *<br>Note that "filename", "error" and "data" are mutual exclusive.
- *<br>
- *<br>The webcam and preview modes use the same resources, so you can't use them both at the same time. Stop one mode before starting the other.
- *
- *
- */
-public class WebCamFacade extends RpcReceiver {
-
-  private final Service mService;
-  private final Executor mJpegCompressionExecutor = new SingleThreadExecutor();
-  private final ByteArrayOutputStream mJpegCompressionBuffer = new ByteArrayOutputStream();
-
-  private volatile byte[] mJpegData;
-
-  private CountDownLatch mJpegDataReady;
-  private boolean mStreaming;
-  private int mPreviewHeight;
-  private int mPreviewWidth;
-  private int mJpegQuality;
-
-  private MjpegServer mJpegServer;
-  private FutureActivityTask<SurfaceHolder> mPreviewTask;
-  private Camera mCamera;
-  private Parameters mParameters;
-  private final EventFacade mEventFacade;
-  private boolean mPreview;
-  private File mDest;
-
-  private final PreviewCallback mPreviewCallback = new PreviewCallback() {
-    @Override
-    public void onPreviewFrame(final byte[] data, final Camera camera) {
-      mJpegCompressionExecutor.execute(new Runnable() {
-        @Override
-        public void run() {
-          mJpegData = compressYuvToJpeg(data);
-          mJpegDataReady.countDown();
-          if (mStreaming) {
-            camera.setOneShotPreviewCallback(mPreviewCallback);
-          }
-        }
-      });
-    }
-  };
-
-  private final PreviewCallback mPreviewEvent = new PreviewCallback() {
-    @Override
-    public void onPreviewFrame(final byte[] data, final Camera camera) {
-      mJpegCompressionExecutor.execute(new Runnable() {
-        @Override
-        public void run() {
-          mJpegData = compressYuvToJpeg(data);
-          Map<String,Object> map = new HashMap<String, Object>();
-          map.put("format", "jpeg");
-          map.put("width", mPreviewWidth);
-          map.put("height", mPreviewHeight);
-          map.put("quality", mJpegQuality);
-          if (mDest!=null) {
-            try {
-              File dest=File.createTempFile("prv",".jpg",mDest);
-              OutputStream output = new FileOutputStream(dest);
-              output.write(mJpegData);
-              output.close();
-              map.put("encoding","file");
-              map.put("filename",dest.toString());
-            } catch (IOException e) {
-              map.put("error", e.toString());
-            }
-          }
-          else {
-            map.put("encoding","Base64");
-            map.put("data", Base64.encodeToString(mJpegData, Base64.DEFAULT));
-          }
-          mEventFacade.postEvent("preview", map);
-          if (mPreview) {
-            camera.setOneShotPreviewCallback(mPreviewEvent);
-          }
-        }
-      });
-    }
-  };
-
-  public WebCamFacade(FacadeManager manager) {
-    super(manager);
-    mService = manager.getService();
-    mJpegDataReady = new CountDownLatch(1);
-    mEventFacade = manager.getReceiver(EventFacade.class);
-  }
-
-  private byte[] compressYuvToJpeg(final byte[] yuvData) {
-    mJpegCompressionBuffer.reset();
-    YuvImage yuvImage =
-        new YuvImage(yuvData, ImageFormat.NV21, mPreviewWidth, mPreviewHeight, null);
-    yuvImage.compressToJpeg(new Rect(0, 0, mPreviewWidth, mPreviewHeight), mJpegQuality,
-        mJpegCompressionBuffer);
-    return mJpegCompressionBuffer.toByteArray();
-  }
-
-  @Rpc(description = "Starts an MJPEG stream and returns a Tuple of address and port for the stream.")
-  public InetSocketAddress webcamStart(
-      @RpcParameter(name = "resolutionLevel", description = "increasing this number provides higher resolution") @RpcDefault("0") Integer resolutionLevel,
-      @RpcParameter(name = "jpegQuality", description = "a number from 0-100") @RpcDefault("20") Integer jpegQuality,
-      @RpcParameter(name = "port", description = "If port is specified, the webcam service will bind to port, otherwise it will pick any available port.") @RpcDefault("0") Integer port)
-      throws Exception {
-    try {
-      openCamera(resolutionLevel, jpegQuality);
-      return startServer(port);
-    } catch (Exception e) {
-      webcamStop();
-      throw e;
-    }
-  }
-
-  private InetSocketAddress startServer(Integer port) {
-    mJpegServer = new MjpegServer(new JpegProvider() {
-      @Override
-      public byte[] getJpeg() {
-        try {
-          mJpegDataReady.await();
-        } catch (InterruptedException e) {
-          Log.e(e);
-        }
-        return mJpegData;
-      }
-    });
-    mJpegServer.addObserver(new SimpleServerObserver() {
-      @Override
-      public void onDisconnect() {
-        if (mJpegServer.getNumberOfConnections() == 0 && mStreaming) {
-          stopStream();
-        }
-      }
-
-      @Override
-      public void onConnect() {
-        if (!mStreaming) {
-          startStream();
-        }
-      }
-    });
-    return mJpegServer.startPublic(port);
-  }
-
-  private void stopServer() {
-    if (mJpegServer != null) {
-      mJpegServer.shutdown();
-      mJpegServer = null;
-    }
-  }
-
-  @Rpc(description = "Adjusts the quality of the webcam stream while it is running.")
-  public void webcamAdjustQuality(
-      @RpcParameter(name = "resolutionLevel", description = "increasing this number provides higher resolution") @RpcDefault("0") Integer resolutionLevel,
-      @RpcParameter(name = "jpegQuality", description = "a number from 0-100") @RpcDefault("20") Integer jpegQuality)
-      throws Exception {
-    if (mStreaming == false) {
-      throw new IllegalStateException("Webcam not streaming.");
-    }
-    stopStream();
-    releaseCamera();
-    openCamera(resolutionLevel, jpegQuality);
-    startStream();
-  }
-
-  private void openCamera(Integer resolutionLevel, Integer jpegQuality) throws IOException,
-      InterruptedException {
-    mCamera = Camera.open();
-    mParameters = mCamera.getParameters();
-    mParameters.setPictureFormat(ImageFormat.JPEG);
-    mParameters.setPreviewFormat(ImageFormat.JPEG);
-    List<Size> supportedPreviewSizes = mParameters.getSupportedPreviewSizes();
-    Collections.sort(supportedPreviewSizes, new Comparator<Size>() {
-      @Override
-      public int compare(Size o1, Size o2) {
-        return o1.width - o2.width;
-      }
-    });
-    Size previewSize =
-        supportedPreviewSizes.get(Math.min(resolutionLevel, supportedPreviewSizes.size() - 1));
-    mPreviewHeight = previewSize.height;
-    mPreviewWidth = previewSize.width;
-    mParameters.setPreviewSize(mPreviewWidth, mPreviewHeight);
-    mJpegQuality = Math.min(Math.max(jpegQuality, 0), 100);
-    mCamera.setParameters(mParameters);
-    // TODO(damonkohler): Rotate image based on orientation.
-    mPreviewTask = createPreviewTask();
-    mCamera.startPreview();
-  }
-
-  private void startStream() {
-    mStreaming = true;
-    mCamera.setOneShotPreviewCallback(mPreviewCallback);
-  }
-
-  private void stopStream() {
-    mJpegDataReady = new CountDownLatch(1);
-    mStreaming = false;
-    if (mPreviewTask != null) {
-      mPreviewTask.finish();
-      mPreviewTask = null;
-    }
-  }
-
-  private void releaseCamera() {
-    if (mCamera != null) {
-      mCamera.release();
-      mCamera = null;
-    }
-    mParameters = null;
-  }
-
-  @Rpc(description = "Stops the webcam stream.")
-  public void webcamStop() {
-    stopServer();
-    stopStream();
-    releaseCamera();
-  }
-
-  private FutureActivityTask<SurfaceHolder> createPreviewTask() throws IOException,
-      InterruptedException {
-    FutureActivityTask<SurfaceHolder> task = new FutureActivityTask<SurfaceHolder>() {
-      @Override
-      public void onCreate() {
-        super.onCreate();
-        final SurfaceView view = new SurfaceView(getActivity());
-        getActivity().setContentView(view);
-        getActivity().getWindow().setSoftInputMode(
-            WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
-        //view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
-        view.getHolder().addCallback(new Callback() {
-          @Override
-          public void surfaceDestroyed(SurfaceHolder holder) {
-          }
-
-          @Override
-          public void surfaceCreated(SurfaceHolder holder) {
-            setResult(view.getHolder());
-          }
-
-          @Override
-          public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-          }
-        });
-      }
-    };
-    FutureActivityTaskExecutor taskExecutor =
-        ((BaseApplication) mService.getApplication()).getTaskExecutor();
-    taskExecutor.execute(task);
-    mCamera.setPreviewDisplay(task.getResult());
-    return task;
-  }
-
-  @Rpc(description = "Start Preview Mode. Throws 'preview' events.",returns="True if successful")
-  public boolean cameraStartPreview(
-          @RpcParameter(name = "resolutionLevel", description = "increasing this number provides higher resolution") @RpcDefault("0") Integer resolutionLevel,
-          @RpcParameter(name = "jpegQuality", description = "a number from 0-100") @RpcDefault("20") Integer jpegQuality,
-          @RpcParameter(name = "filepath", description = "Path to store jpeg files.") @RpcOptional String filepath)
-      throws InterruptedException {
-    mDest=null;
-    if (filepath!=null && (filepath.length()>0)) {
-      mDest = new File(filepath);
-      if (!mDest.exists()) mDest.mkdirs();
-      if (!(mDest.isDirectory() && mDest.canWrite())) {
-        return false;
-      }
-    }
-
-    try {
-      openCamera(resolutionLevel, jpegQuality);
-    } catch (IOException e) {
-      Log.e(e);
-      return false;
-    }
-    startPreview();
-    return true;
-  }
-
-  @Rpc(description = "Stop the preview mode.")
-  public void cameraStopPreview() {
-    stopPreview();
-  }
-
-  private void startPreview() {
-    mPreview = true;
-    mCamera.setOneShotPreviewCallback(mPreviewEvent);
-  }
-
-  private void stopPreview() {
-    mPreview = false;
-    if (mPreviewTask!=null)
-    {
-      mPreviewTask.finish();
-      mPreviewTask=null;
-    }
-    releaseCamera();
-  }
-
-  @Override
-  public void shutdown() {
-    mPreview=false;
-    webcamStop();
-  }
-}
diff --git a/OWNERS b/OWNERS
index 599f5dd..8c3141f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,10 +1,12 @@
+ashutoshrsingh@google.com
 bettyzhou@google.com
 bmahadev@google.com
+bpeake@google.com
 etancohen@google.com
 gmoturu@google.com
+jaineelm@google.com
 jpawlowski@google.com
 krisr@google.com
-tturney@google.com
 markdr@google.com
 sutherlandsean@google.com
-bpeake@google.com
+tturney@google.com
diff --git a/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java b/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java
index a2dfb70..68d91b0 100644
--- a/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java
+++ b/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java
@@ -16,16 +16,6 @@
 
 package com.googlecode.android_scripting.facade;
 
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
 import com.google.common.collect.Maps;
 import com.googlecode.android_scripting.Log;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothA2dpFacade;
@@ -35,8 +25,8 @@
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothHealthFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothHfpClientFacade;
-import com.googlecode.android_scripting.facade.bluetooth.BluetoothHidFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothHidDeviceFacade;
+import com.googlecode.android_scripting.facade.bluetooth.BluetoothHidFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothHspFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothLeAdvertiseFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothLeAdvertisingSetFacade;
@@ -44,8 +34,8 @@
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothMapClientFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothMapFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothMediaFacade;
-import com.googlecode.android_scripting.facade.bluetooth.BluetoothPbapClientFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothPanFacade;
+import com.googlecode.android_scripting.facade.bluetooth.BluetoothPbapClientFacade;
 import com.googlecode.android_scripting.facade.bluetooth.BluetoothRfcommFacade;
 import com.googlecode.android_scripting.facade.bluetooth.GattClientFacade;
 import com.googlecode.android_scripting.facade.bluetooth.GattServerFacade;
@@ -54,6 +44,7 @@
 import com.googlecode.android_scripting.facade.media.MediaRecorderFacade;
 import com.googlecode.android_scripting.facade.media.MediaScannerFacade;
 import com.googlecode.android_scripting.facade.media.MediaSessionFacade;
+import com.googlecode.android_scripting.facade.net.IpSecManagerFacade;
 import com.googlecode.android_scripting.facade.net.nsd.NsdManagerFacade;
 import com.googlecode.android_scripting.facade.telephony.CarrierConfigFacade;
 import com.googlecode.android_scripting.facade.telephony.ImsManagerFacade;
@@ -63,9 +54,10 @@
 import com.googlecode.android_scripting.facade.telephony.TelecomManagerFacade;
 import com.googlecode.android_scripting.facade.telephony.TelephonyManagerFacade;
 import com.googlecode.android_scripting.facade.ui.UiFacade;
+import com.googlecode.android_scripting.facade.webcam.WebCamFacade;
 import com.googlecode.android_scripting.facade.wifi.HttpFacade;
-import com.googlecode.android_scripting.facade.wifi.WifiManagerFacade;
 import com.googlecode.android_scripting.facade.wifi.WifiAwareManagerFacade;
+import com.googlecode.android_scripting.facade.wifi.WifiManagerFacade;
 import com.googlecode.android_scripting.facade.wifi.WifiP2pManagerFacade;
 import com.googlecode.android_scripting.facade.wifi.WifiRttManagerFacade;
 import com.googlecode.android_scripting.facade.wifi.WifiScannerFacade;
@@ -75,8 +67,16 @@
 import com.googlecode.android_scripting.rpc.RpcMinSdk;
 import com.googlecode.android_scripting.rpc.RpcStartEvent;
 import com.googlecode.android_scripting.rpc.RpcStopEvent;
-import com.googlecode.android_scripting.webcam.WebCamFacade;
-import com.googlecode.android_scripting.facade.net.IpSecManagerFacade;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
 
 /**
  * Encapsulates the list of supported facades and their construction.
diff --git a/ScriptingLayerForAndroid/Android.mk b/ScriptingLayerForAndroid/Android.mk
index 8bbbe77..93a2edb 100644
--- a/ScriptingLayerForAndroid/Android.mk
+++ b/ScriptingLayerForAndroid/Android.mk
@@ -22,6 +22,7 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_PACKAGE_NAME := sl4a
+LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_MODULE_OWNER := google
 LOCAL_DEX_PREOPT := false
 
diff --git a/ScriptingLayerForAndroid/AndroidManifest.xml b/ScriptingLayerForAndroid/AndroidManifest.xml
index 0d80394..69d1e6e 100644
--- a/ScriptingLayerForAndroid/AndroidManifest.xml
+++ b/ScriptingLayerForAndroid/AndroidManifest.xml
@@ -120,8 +120,12 @@
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
     <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
     <uses-permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST"/>
-    <application android:icon="@drawable/sl4a_logo_48" android:label="@string/application_title" android:name=".Sl4aApplication"
-        android:theme="@android:style/Theme.DeviceDefault">
+    <application
+        android:icon="@drawable/sl4a_logo_48"
+        android:label="@string/application_title"
+        android:name=".Sl4aApplication"
+        android:theme="@android:style/Theme.DeviceDefault"
+        android:usesCleartextTraffic="true">
         <activity android:name=".activity.ScriptManager" android:configChanges="keyboardHidden|orientation" android:windowSoftInputMode="adjustResize" android:launchMode="singleTop">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />