Merge "Add bt anomaly action in testing app" into oc-mr1-dev
diff --git a/tests/anomaly-tester/AndroidManifest.xml b/tests/anomaly-tester/AndroidManifest.xml
index b5f50c4..c057a66 100644
--- a/tests/anomaly-tester/AndroidManifest.xml
+++ b/tests/anomaly-tester/AndroidManifest.xml
@@ -14,8 +14,13 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.settings.anomaly.tester">
+          package="com.android.settings.anomaly.tester">
 
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
     <application
         android:allowBackup="false"
         android:label="@string/app_name"
@@ -29,6 +34,10 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <service
+            android:name=".service.AnomalyService"
+            android:exported="false"/>
     </application>
 
 </manifest>
\ No newline at end of file
diff --git a/tests/anomaly-tester/res/layout/bluetooth_anomaly.xml b/tests/anomaly-tester/res/layout/bluetooth_anomaly.xml
index 7de558e..87e61b0 100644
--- a/tests/anomaly-tester/res/layout/bluetooth_anomaly.xml
+++ b/tests/anomaly-tester/res/layout/bluetooth_anomaly.xml
@@ -35,16 +35,21 @@
             android:layout_width="0dp"
             android:layout_weight="3"
             android:layout_height="wrap_content"
-            android:hint="Threshold(ms)"/>
+            android:hint="Threshold(ms)"
+            android:text="3000"
+            android:inputType="number"/>
 
         <EditText
             android:id="@+id/bluetooth_run_time"
             android:layout_width="0dp"
             android:layout_weight="3"
             android:layout_height="wrap_content"
-            android:hint="Run time(ms)"/>
+            android:hint="Run time(ms)"
+            android:text="6000"
+            android:inputType="number"/>
 
         <Button
+            android:id="@+id/bluetooth_button"
             android:layout_width="0dp"
             android:layout_weight="2"
             android:layout_height="wrap_content"
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/AnomalyActivity.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/AnomalyActivity.java
index 6aa8edd..773dd27 100644
--- a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/AnomalyActivity.java
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/AnomalyActivity.java
@@ -15,18 +15,92 @@
 package com.android.settings.anomaly.tester;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.provider.Settings;
+import android.util.Log;
 import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
 
+import com.android.settings.anomaly.tester.service.AnomalyService;
+import com.android.settings.anomaly.tester.utils.AnomalyActions;
+import com.android.settings.anomaly.tester.utils.AnomalyPolicyBuilder;
+
+/**
+ * Main activity to control and start anomaly
+ */
 public class AnomalyActivity extends Activity {
+    private static final String TAG = AnomalyActivity.class.getSimpleName();
+
+    public static final String KEY_TARGET_BUTTON = "target_button";
+
+    private AnomalyResultReceiver mResultReceiver;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
+        mResultReceiver = new AnomalyResultReceiver(new Handler());
     }
 
     public void startBluetoothAnomaly(View view) {
-        // Add in future cl
+        try {
+            // Enable anomaly detection and change the threshold
+            final String config = new AnomalyPolicyBuilder()
+                    .addPolicy(AnomalyPolicyBuilder.KEY_ANOMALY_DETECTION_ENABLED, true)
+                    .addPolicy(AnomalyPolicyBuilder.KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, true)
+                    .addPolicy(AnomalyPolicyBuilder.KEY_BLUETOOTH_SCAN_THRESHOLD,
+                            getValueFromEditText(R.id.bluetooth_threshold))
+                    .build();
+            Settings.Global.putString(getContentResolver(),
+                    Settings.Global.ANOMALY_DETECTION_CONSTANTS, config);
+
+            // Start the anomaly service
+            Intent intent = new Intent(this, AnomalyService.class);
+            intent.putExtra(AnomalyActions.KEY_ACTION, AnomalyActions.ACTION_BLE_SCAN_UNOPTIMIZED);
+            intent.putExtra(AnomalyActions.KEY_DURATION_MS,
+                    getValueFromEditText(R.id.bluetooth_run_time));
+            intent.putExtra(AnomalyActions.KEY_RESULT_RECEIVER, mResultReceiver);
+            intent.putExtra(KEY_TARGET_BUTTON, view.getId());
+            startService(intent);
+
+            view.setEnabled(false);
+        } catch (NumberFormatException e) {
+            Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private long getValueFromEditText(final int id) throws NumberFormatException {
+        final EditText editText = findViewById(id);
+        if (editText != null) {
+            final long value = Long.parseLong(editText.getText().toString());
+            if (value > 0) {
+                return value;
+            }
+        }
+
+        throw new NumberFormatException("Number should be positive");
+    }
+
+    private class AnomalyResultReceiver extends ResultReceiver {
+
+        public AnomalyResultReceiver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            super.onReceiveResult(resultCode, resultData);
+
+            final Button button = findViewById(resultData.getInt(KEY_TARGET_BUTTON));
+            if (button != null) {
+                button.setEnabled(true);
+            }
+
+        }
     }
 }
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/service/AnomalyService.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/service/AnomalyService.java
new file mode 100644
index 0000000..b569bce
--- /dev/null
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/service/AnomalyService.java
@@ -0,0 +1,49 @@
+/*
+ * 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.android.settings.anomaly.tester.service;
+
+import android.annotation.Nullable;
+import android.app.IntentService;
+import android.content.Intent;
+import android.os.ResultReceiver;
+
+import com.android.settings.anomaly.tester.utils.AnomalyActions;
+
+/**
+ * Service to run the anomaly action
+ */
+public class AnomalyService extends IntentService {
+    private static final String TAG = AnomalyService.class.getSimpleName();
+
+    public AnomalyService() {
+        super(AnomalyService.class.getSimpleName());
+    }
+
+    @Override
+    protected void onHandleIntent(@Nullable Intent intent) {
+        final String action = intent.getStringExtra(AnomalyActions.KEY_ACTION);
+        final long durationMs = intent.getLongExtra(AnomalyActions.KEY_DURATION_MS, 0);
+        final ResultReceiver resultReceiver = intent.getParcelableExtra(
+                AnomalyActions.KEY_RESULT_RECEIVER);
+
+        AnomalyActions.doAction(this, action, durationMs);
+
+        if (resultReceiver != null) {
+            resultReceiver.send(0 /* resultCode */, intent.getExtras());
+        }
+    }
+}
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyActions.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyActions.java
new file mode 100644
index 0000000..a55efdb
--- /dev/null
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyActions.java
@@ -0,0 +1,96 @@
+/*
+ * 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.android.settings.anomaly.tester.utils;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Actions to generate anomaly.
+ */
+public class AnomalyActions {
+    private static final String TAG = AnomalyActions.class.getSimpleName();
+
+    public static final String KEY_ACTION = "action";
+    public static final String KEY_DURATION_MS = "duration_ms";
+    public static final String KEY_RESULT_RECEIVER = "result_receiver";
+
+    public static final String ACTION_BLE_SCAN_UNOPTIMIZED = "action.ble_scan_unoptimized";
+
+    public static void doAction(Context ctx, String actionCode, long durationMs) {
+        if (actionCode == null) {
+            Log.e(TAG, "Intent was missing action.");
+            return;
+        }
+        switch (actionCode) {
+            case ACTION_BLE_SCAN_UNOPTIMIZED:
+                doUnoptimizedBleScan(ctx, durationMs);
+                break;
+            default:
+                Log.e(TAG, "Intent had invalid action");
+        }
+    }
+
+    private static void doUnoptimizedBleScan(Context ctx, long durationMs) {
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+
+        // perform ble scanning
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled() ) {
+            Log.e(TAG, "Device does not support Bluetooth or Bluetooth not enabled");
+            return;
+        }
+        BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
+        if (bleScanner == null) {
+            Log.e(TAG, "Cannot access BLE scanner");
+            return;
+        }
+
+        ScanCallback scanCallback = new ScanCallback() {
+            @Override
+            public void onScanResult(int callbackType, ScanResult result) {
+                Log.v(TAG, "called onScanResult");
+            }
+
+            @Override
+            public void onScanFailed(int errorCode) {
+                Log.v(TAG, "called onScanFailed");
+            }
+
+            @Override
+            public void onBatchScanResults(List<ScanResult> results) {
+                Log.v(TAG, "called onBatchScanResults");
+            }
+        };
+
+        bleScanner.startScan(null, scanSettings, scanCallback);
+        try {
+            Thread.sleep(durationMs);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Thread couldn't sleep for " + durationMs, e);
+        }
+        bleScanner.stopScan(scanCallback);
+    }
+}
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyPolicyBuilder.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyPolicyBuilder.java
new file mode 100644
index 0000000..bf8e075
--- /dev/null
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyPolicyBuilder.java
@@ -0,0 +1,72 @@
+/*
+ * 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.android.settings.anomaly.tester.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Builder to build the anomaly policy string, used in {@link android.provider.Settings.Global}
+ *
+ * @see android.provider.Settings.Global#ANOMALY_DETECTION_CONSTANTS
+ */
+public class AnomalyPolicyBuilder {
+    public static final String KEY_ANOMALY_DETECTION_ENABLED = "anomaly_detection_enabled";
+    public static final String KEY_WAKELOCK_DETECTION_ENABLED = "wakelock_enabled";
+    public static final String KEY_WAKEUP_ALARM_DETECTION_ENABLED = "wakeup_alarm_enabled";
+    public static final String KEY_BLUETOOTH_SCAN_DETECTION_ENABLED = "bluetooth_scan_enabled";
+    public static final String KEY_WAKELOCK_THRESHOLD = "wakelock_threshold";
+    public static final String KEY_WAKEUP_ALARM_THRESHOLD = "wakeup_alarm_threshold";
+    public static final String KEY_BLUETOOTH_SCAN_THRESHOLD = "bluetooth_scan_threshold";
+
+    public static final String DELIM = ",";
+
+    private Map<String, String> mValues;
+
+    public AnomalyPolicyBuilder() {
+        mValues = new HashMap<>();
+    }
+
+    public AnomalyPolicyBuilder addPolicy(String key, String value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public AnomalyPolicyBuilder addPolicy(String key, long value) {
+        mValues.put(key, Long.toString(value));
+        return this;
+    }
+
+
+    public AnomalyPolicyBuilder addPolicy(String key, boolean value) {
+        mValues.put(key, value ? "true" : "false");
+        return this;
+    }
+
+    public String build() {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : mValues.entrySet()) {
+            sb.append(entry.getKey() + "=" + entry.getValue() + DELIM);
+        }
+
+        if (sb.length() != 0) {
+            return sb.substring(0, sb.length() - 1);
+        } else {
+            return "";
+        }
+    }
+}