Adds interval and all other details for WifiRTT scanning.

Bug: 111830148
Test: Manually tested.

Change-Id: I8ee53df3cb08e151713f0be98e5778a04e1c2f7b
diff --git a/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/AccessPointRangingResultsActivity.java b/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/AccessPointRangingResultsActivity.java
index ca14774..cced739 100644
--- a/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/AccessPointRangingResultsActivity.java
+++ b/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/AccessPointRangingResultsActivity.java
@@ -15,72 +15,320 @@
  */
 package com.example.android.wifirttscan;
 
+import android.Manifest.permission;
+import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.net.wifi.ScanResult;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.net.wifi.rtt.WifiRttManager;
 import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
 import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
 import android.widget.EditText;
 import android.widget.TextView;
+import android.widget.Toast;
 
-/** Displays ranging information about a particular access point chosen by the user. */
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Displays ranging information about a particular access point chosen by the user. Uses {@link
+ * Handler} to trigger new requests based on
+ */
 public class AccessPointRangingResultsActivity extends AppCompatActivity {
     private static final String TAG = "APRRActivity";
 
     public static final String SCAN_RESULT_EXTRA =
             "com.example.android.wifirttscan.extra.SCAN_RESULT";
 
-    private static final int STATISTIC_WINDOW_SIZE_DEFAULT = 50;
-    private static final int RANGING_PERIOD_MILLISECONDS_DEFAULT = 1000;
+    private static final int SAMPLE_SIZE_DEFAULT = 50;
+    private static final int MILLISECONDS_DELAY_BEFORE_NEW_RANGING_REQUEST_DEFAULT = 1000;
 
-    private ScanResult mScanResult;
-
+    // UI Elements.
     private TextView mSsidTextView;
     private TextView mBssidTextView;
 
-    private TextView mRange;
-    private TextView mRangeMean;
-    private TextView mRangeSD;
-    private TextView mRangeSDMean;
-    private TextView mRssi;
-    private TextView mSuccessesInBurst;
-    private TextView mSuccessRatio;
+    private TextView mRangeTextView;
+    private TextView mRangeMeanTextView;
+    private TextView mRangeSDTextView;
+    private TextView mRangeSDMeanTextView;
+    private TextView mRssiTextView;
+    private TextView mSuccessesInBurstTextView;
+    private TextView mSuccessRatioTextView;
+    private TextView mNumberOfRequestsTextView;
 
-    private EditText mStatsWindowSize;
-    private EditText mRangingPeriod;
+    private EditText mSampleSizeEditText;
+    private EditText mMillisecondsDelayBeforeNewRangingRequestEditText;
 
-    private int mStatisticWindowSize;
-    private int mRangingPeriodMilliseconds;
+    // Non UI variables.
+    private ScanResult mScanResult;
+    private String mMAC;
+
+    private int mNumberOfRangeRequests;
+    private int mNumberOfSuccessfulRangeRequests;
+
+    private int mMillisecondsDelayBeforeNewRangingRequest;
+
+    // Max sample size to calculate average for
+    // 1. Distance to device (getDistanceMm) over time
+    // 2. Standard deviation of the measured distance to the device (getDistanceStdDevMm) over time
+    // Note: A RangeRequest result already consists of the average of 7 readings from a burst,
+    // so the average in (1) is the average of these averages.
+    private int mSampleSize;
+
+    // Used to loop over a list of distances to calculate averages (ensures data structure never
+    // get larger than sample size).
+    private int mStatisticRangeHistoryEndIndex;
+    private ArrayList<Integer> mStatisticRangeHistory;
+
+    // Used to loop over a list of the standard deviation of the measured distance to calculate
+    // averages  (ensures data structure never get larger than sample size).
+    private int mStatisticRangeSDHistoryEndIndex;
+    private ArrayList<Integer> mStatisticRangeSDHistory;
+
+    private WifiRttManager mWifiRttManager;
+    private RttRangingResultCallback mRttRangingResultCallback;
+
+    // Triggers additional RangingRequests with delay (mMillisecondsDelayBeforeNewRangingRequest).
+    final Handler mRangeRequestDelayHandler = new Handler();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_access_point_ranging_results);
 
+        // Initializes UI elements.
         mSsidTextView = findViewById(R.id.ssid);
         mBssidTextView = findViewById(R.id.bssid);
 
-        mRange = findViewById(R.id.range_value);
-        mRangeMean = findViewById(R.id.range_mean_value);
-        mRangeSD = findViewById(R.id.range_sd_value);
-        mRangeSDMean = findViewById(R.id.range_sd_mean_value);
-        mRssi = findViewById(R.id.rssi_value);
-        mSuccessesInBurst = findViewById(R.id.successes_in_burst_value);
-        mSuccessRatio = findViewById(R.id.success_ratio_value);
+        mRangeTextView = findViewById(R.id.range_value);
+        mRangeMeanTextView = findViewById(R.id.range_mean_value);
+        mRangeSDTextView = findViewById(R.id.range_sd_value);
+        mRangeSDMeanTextView = findViewById(R.id.range_sd_mean_value);
+        mRssiTextView = findViewById(R.id.rssi_value);
+        mSuccessesInBurstTextView = findViewById(R.id.successes_in_burst_value);
+        mSuccessRatioTextView = findViewById(R.id.success_ratio_value);
+        mNumberOfRequestsTextView = findViewById(R.id.number_of_requests_value);
 
-        mStatsWindowSize = findViewById(R.id.stats_window_size_edit_value);
-        mStatisticWindowSize = STATISTIC_WINDOW_SIZE_DEFAULT;
-        mStatsWindowSize.setText(mStatisticWindowSize + "");
+        mSampleSizeEditText = findViewById(R.id.stats_window_size_edit_value);
+        mSampleSizeEditText.setText(SAMPLE_SIZE_DEFAULT + "");
 
-        mRangingPeriod = findViewById(R.id.ranging_period_edit_value);
-        mRangingPeriodMilliseconds = RANGING_PERIOD_MILLISECONDS_DEFAULT;
-        mRangingPeriod.setText(mRangingPeriodMilliseconds + "");
+        mMillisecondsDelayBeforeNewRangingRequestEditText =
+                findViewById(R.id.ranging_period_edit_value);
+        mMillisecondsDelayBeforeNewRangingRequestEditText.setText(
+                MILLISECONDS_DELAY_BEFORE_NEW_RANGING_REQUEST_DEFAULT + "");
 
+        // Retrieve ScanResult from Intent.
         Intent intent = getIntent();
         mScanResult = intent.getParcelableExtra(SCAN_RESULT_EXTRA);
 
+        if (mScanResult == null) {
+            finish();
+        }
+
+        mMAC = mScanResult.BSSID;
+
         mSsidTextView.setText(mScanResult.SSID);
         mBssidTextView.setText(mScanResult.BSSID);
 
-        // TODO (jewalker): Implement Ranging Request.
+        mWifiRttManager = (WifiRttManager) getSystemService(Context.WIFI_RTT_RANGING_SERVICE);
+        mRttRangingResultCallback = new RttRangingResultCallback();
+
+        // Used to store range (distance) and rangeSd (standard deviation of the measured distance)
+        // history to calculate averages.
+        mStatisticRangeHistory = new ArrayList<>();
+        mStatisticRangeSDHistory = new ArrayList<>();
+
+        resetData();
+
+        startRangingRequest();
+    }
+
+    private void resetData() {
+        mSampleSize = Integer.parseInt(mSampleSizeEditText.getText().toString());
+
+        mMillisecondsDelayBeforeNewRangingRequest =
+                Integer.parseInt(
+                        mMillisecondsDelayBeforeNewRangingRequestEditText.getText().toString());
+
+        mNumberOfSuccessfulRangeRequests = 0;
+        mNumberOfRangeRequests = 0;
+
+        mStatisticRangeHistoryEndIndex = 0;
+        mStatisticRangeHistory.clear();
+
+        mStatisticRangeSDHistoryEndIndex = 0;
+        mStatisticRangeSDHistory.clear();
+    }
+
+    private void startRangingRequest() {
+        // Permission for fine location should already be granted via MainActivity (you can't get
+        // to this class unless you already have permission. If they get to this class, then disable
+        // fine location permission, we kick them back to main activity.
+        if (ActivityCompat.checkSelfPermission(this, permission.ACCESS_FINE_LOCATION)
+                != PackageManager.PERMISSION_GRANTED) {
+            finish();
+        }
+
+        mNumberOfRangeRequests++;
+
+        RangingRequest rangingRequest =
+                new RangingRequest.Builder().addAccessPoint(mScanResult).build();
+
+        mWifiRttManager.startRanging(
+                rangingRequest, getApplication().getMainExecutor(), mRttRangingResultCallback);
+    }
+
+    // Calculates average distance based on stored history.
+    private float getDistanceMean() {
+        float distanceSum = 0;
+
+        for (int distance : mStatisticRangeHistory) {
+            distanceSum += distance;
+        }
+
+        return distanceSum / mStatisticRangeHistory.size();
+    }
+
+    // Adds distance to history. If larger than sample size value, loops back over and replaces the
+    // oldest distance record in the list.
+    private void addDistanceToHistory(int distance) {
+
+        if (mStatisticRangeHistory.size() >= mSampleSize) {
+
+            if (mStatisticRangeHistoryEndIndex >= mSampleSize) {
+                mStatisticRangeHistoryEndIndex = 0;
+            }
+
+            mStatisticRangeHistory.set(mStatisticRangeHistoryEndIndex, distance);
+            mStatisticRangeHistoryEndIndex++;
+
+        } else {
+            mStatisticRangeHistory.add(distance);
+        }
+    }
+
+    // Calculates standard deviation of the measured distance based on stored history.
+    private float getStandardDeviationOfDistanceMean() {
+        float distanceSdSum = 0;
+
+        for (int distanceSd : mStatisticRangeSDHistory) {
+            distanceSdSum += distanceSd;
+        }
+
+        return distanceSdSum / mStatisticRangeHistory.size();
+    }
+
+    // Adds standard deviation of the measured distance to history. If larger than sample size
+    // value, loops back over and replaces the oldest distance record in the list.
+    private void addStandardDeviationOfDistanceToHistory(int distanceSd) {
+
+        if (mStatisticRangeSDHistory.size() >= mSampleSize) {
+
+            if (mStatisticRangeSDHistoryEndIndex >= mSampleSize) {
+                mStatisticRangeSDHistoryEndIndex = 0;
+            }
+
+            mStatisticRangeSDHistory.set(mStatisticRangeSDHistoryEndIndex, distanceSd);
+            mStatisticRangeSDHistoryEndIndex++;
+
+        } else {
+            mStatisticRangeSDHistory.add(distanceSd);
+        }
+    }
+
+    public void onResetButtonClick(View view) {
+        resetData();
+    }
+
+    // Class that handles callbacks for all RangingRequests and issues new RangingRequests.
+    private class RttRangingResultCallback extends RangingResultCallback {
+
+        private void queueNextRangingRequest() {
+            mRangeRequestDelayHandler.postDelayed(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            startRangingRequest();
+                        }
+                    },
+                    mMillisecondsDelayBeforeNewRangingRequest);
+        }
+
+        @Override
+        public void onRangingFailure(int code) {
+            Log.d(TAG, "onRangingFailure() code: " + code);
+            queueNextRangingRequest();
+        }
+
+        @Override
+        public void onRangingResults(@NonNull List<RangingResult> list) {
+            Log.d(TAG, "onRangingResults(): " + list);
+
+            // Because we are only requesting RangingResult for one access point (not multiple
+            // access points), this will only ever be one. (Use loops when requesting RangingResults
+            // for multiple access points.)
+            if (list.size() == 1) {
+
+                RangingResult rangingResult = list.get(0);
+
+                if (mMAC.equals(rangingResult.getMacAddress().toString())) {
+
+                    if (rangingResult.getStatus() == RangingResult.STATUS_SUCCESS) {
+
+                        mNumberOfSuccessfulRangeRequests++;
+
+                        mRangeTextView.setText((rangingResult.getDistanceMm() / 1000f) + "");
+                        addDistanceToHistory(rangingResult.getDistanceMm());
+                        mRangeMeanTextView.setText((getDistanceMean() / 1000f) + "");
+
+                        mRangeSDTextView.setText(
+                                (rangingResult.getDistanceStdDevMm() / 1000f) + "");
+                        addStandardDeviationOfDistanceToHistory(
+                                rangingResult.getDistanceStdDevMm());
+                        mRangeSDMeanTextView.setText(
+                                (getStandardDeviationOfDistanceMean() / 1000f) + "");
+
+                        mRssiTextView.setText(rangingResult.getRssi() + "");
+                        mSuccessesInBurstTextView.setText(
+                                rangingResult.getNumSuccessfulMeasurements()
+                                        + "/"
+                                        + rangingResult.getNumAttemptedMeasurements());
+
+                        float successRatio =
+                                ((float) mNumberOfSuccessfulRangeRequests
+                                                / (float) mNumberOfRangeRequests)
+                                        * 100;
+                        mSuccessRatioTextView.setText(successRatio + "%");
+
+                        mNumberOfRequestsTextView.setText(mNumberOfRangeRequests + "");
+
+                    } else if (rangingResult.getStatus()
+                            == RangingResult.STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC) {
+                        Log.d(TAG, "RangingResult failed (AP doesn't support IEEE80211 MC.");
+
+                    } else {
+                        Log.d(TAG, "RangingResult failed.");
+                    }
+
+                } else {
+                    Toast.makeText(
+                                    getApplicationContext(),
+                                    R.string
+                                            .mac_mismatch_message_activity_access_point_ranging_results,
+                                    Toast.LENGTH_LONG)
+                            .show();
+                }
+            }
+
+            queueNextRangingRequest();
+        }
     }
 }
diff --git a/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/MainActivity.java b/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/MainActivity.java
index 44be36d..5312782 100644
--- a/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/MainActivity.java
+++ b/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/MainActivity.java
@@ -168,10 +168,11 @@
 
                     mAdapter.swapData(mAccessPointsSupporting80211mc);
 
-                    logToUi(scanResults.size() +
-                            " APs discovered, " +
-                            mAccessPointsSupporting80211mc.size() +
-                            " RTT capable.");
+                    logToUi(
+                            scanResults.size()
+                                    + " APs discovered, "
+                                    + mAccessPointsSupporting80211mc.size()
+                                    + " RTT capable.");
 
                 } else {
                     // TODO (jewalker): Add Snackbar regarding permissions
diff --git a/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/MyAdapter.java b/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/MyAdapter.java
index 2427f90..8ba8097 100644
--- a/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/MyAdapter.java
+++ b/connectivity/wifirtt/WifiRttScan/Application/src/main/java/com/example/android/wifirttscan/MyAdapter.java
@@ -39,9 +39,7 @@
 
     private List<ScanResult> mWifiAccessPointsWithRtt;
 
-    public MyAdapter(
-            List<ScanResult> list,
-            ScanResultClickListener scanResultClickListener) {
+    public MyAdapter(List<ScanResult> list, ScanResultClickListener scanResultClickListener) {
         mWifiAccessPointsWithRtt = list;
         sScanResultClickListener = scanResultClickListener;
     }
@@ -65,8 +63,7 @@
 
         @Override
         public void onClick(View view) {
-            sScanResultClickListener.onScanResultItemClick(
-                    getItem(getAdapterPosition()));
+            sScanResultClickListener.onScanResultItemClick(getItem(getAdapterPosition()));
         }
     }
 
diff --git a/connectivity/wifirtt/WifiRttScan/Application/src/main/res/layout/activity_access_point_ranging_results.xml b/connectivity/wifirtt/WifiRttScan/Application/src/main/res/layout/activity_access_point_ranging_results.xml
index 70abd39..b74c779 100644
--- a/connectivity/wifirtt/WifiRttScan/Application/src/main/res/layout/activity_access_point_ranging_results.xml
+++ b/connectivity/wifirtt/WifiRttScan/Application/src/main/res/layout/activity_access_point_ranging_results.xml
@@ -240,18 +240,44 @@
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/successes_in_burst_value" />
 
+    <TextView
+        android:id="@+id/number_of_requests_label"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_access_point_ranging_request_margin_start"
+        android:layout_marginTop="@dimen/activity_access_point_ranging_request_margin_top"
+        android:gravity="start"
+        android:text="@string/number_of_requests_label_activity_access_point_ranging_results"
+        android:textAlignment="textStart"
+        android:textSize="@dimen/activity_access_point_ranging_request_item_text_size"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/success_ratio_label" />
+
+    <TextView
+        android:id="@+id/number_of_requests_value"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/activity_access_point_ranging_request_margin_end"
+        android:layout_marginTop="@dimen/activity_access_point_ranging_request_margin_top"
+        android:gravity="end"
+        android:text="@string/activity_access_point_ranging_results_requesting_default"
+        android:textAlignment="textEnd"
+        android:textSize="@dimen/activity_access_point_ranging_request_item_text_size"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/success_ratio_value" />
+
     <View
         android:id="@+id/divider2"
         android:layout_width="0dp"
         android:layout_height="@dimen/activity_access_point_ranging_request_divider_height"
-        android:layout_marginEnd="@dimen/activity_access_point_ranging_request_margin_end"
         android:layout_marginStart="@dimen/activity_access_point_ranging_request_margin_start"
         android:layout_marginTop="@dimen/activity_access_point_ranging_request_margin_top_divider"
+        android:layout_marginEnd="@dimen/activity_access_point_ranging_request_margin_end"
         android:background="?android:attr/listDivider"
         android:visibility="visible"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/success_ratio_value" />
+        app:layout_constraintTop_toBottomOf="@+id/number_of_requests_value" />
 
     <TextView
         android:id="@+id/stats_window_size_label"
@@ -273,6 +299,7 @@
         android:layout_marginEnd="@dimen/activity_access_point_ranging_request_margin_end"
         android:layout_marginTop="@dimen/activity_access_point_ranging_request_margin_top_divider"
         android:ems="10"
+        android:singleLine="true"
         android:inputType="number"
         android:textAlignment="textEnd"
         android:textSize="@dimen/activity_access_point_ranging_request_item_text_size"
@@ -300,6 +327,7 @@
         android:layout_marginEnd="@dimen/activity_access_point_ranging_request_margin_end"
         android:layout_marginTop="@dimen/activity_access_point_ranging_request_margin_top"
         android:ems="10"
+        android:singleLine="true"
         android:inputType="number"
         android:textAlignment="textEnd"
         android:textSize="@dimen/activity_access_point_ranging_request_item_text_size"
@@ -309,13 +337,15 @@
 
     <Button
         android:id="@+id/reset_button"
-        android:layout_width="91dp"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginBottom="8dp"
         android:layout_marginEnd="@dimen/activity_access_point_ranging_request_margin_end"
         android:layout_marginStart="@dimen/activity_access_point_ranging_request_margin_start"
+        android:onClick="onResetButtonClick"
         android:text="@string/reset_label_activity_access_point_ranging_results"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent" />
+
 </android.support.constraint.ConstraintLayout>
diff --git a/connectivity/wifirtt/WifiRttScan/Application/src/main/res/values/strings.xml b/connectivity/wifirtt/WifiRttScan/Application/src/main/res/values/strings.xml
index 1bfda3e..f7d637f 100644
--- a/connectivity/wifirtt/WifiRttScan/Application/src/main/res/values/strings.xml
+++ b/connectivity/wifirtt/WifiRttScan/Application/src/main/res/values/strings.xml
@@ -40,6 +40,8 @@
     <string name="stats_window_size_label_activity_access_point_ranging_results">Stats window size:</string>
     <string name="ranging_period_label_activity_access_point_ranging_results">Ranging period (ms):</string>
 
-    <string name="reset_label_activity_access_point_ranging_results">Reset</string>
+    <string name="reset_label_activity_access_point_ranging_results">Reset Ranging Requests</string>
+    <string name="number_of_requests_label_activity_access_point_ranging_results">Number of requests:</string>
+    <string name="mac_mismatch_message_activity_access_point_ranging_results">Callback MAC address doesn\'t match original request MAC address.</string>
 
 </resources>
diff --git a/connectivity/wifirtt/WifiRttScan/gradle/wrapper/gradle-wrapper.properties b/connectivity/wifirtt/WifiRttScan/gradle/wrapper/gradle-wrapper.properties
index fdc9709..562d1fa 100644
--- a/connectivity/wifirtt/WifiRttScan/gradle/wrapper/gradle-wrapper.properties
+++ b/connectivity/wifirtt/WifiRttScan/gradle/wrapper/gradle-wrapper.properties
@@ -1,19 +1,6 @@
-# Copyright 2018 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.
-#Wed Apr 10 15:27:10 PDT 2013
+#Fri Jul 27 17:47:47 PDT 2018
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-rc-1-all.zip