Handle WiFi Enable/Disable Intents

Add an activity to handle intents to enable/disable wifi.

Bug: 133189568
Test: Manual
Change-Id: I135c3ff6e9e791a3764f96a3c8441cc7ff0bd192
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 10397eb..7479f10 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -324,6 +324,16 @@
             <meta-data android:name="distractionOptimized" android:value="true"/>
         </activity>
 
+        <activity
+            android:name=".wifi.WifiRequestToggleActivity"
+            android:excludeFromRecents="true">
+            <intent-filter>
+                <action android:name="android.net.wifi.action.REQUEST_ENABLE" />
+                <action android:name="android.net.wifi.action.REQUEST_DISABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <!-- This logic is copied from phone.-->
         <!-- Ensures there's lightweight fallback activity when no other MAIN/HOME activity is present.-->
         <activity android:name=".FallbackHome"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3b38e1e..6e5d4a1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -253,6 +253,12 @@
     <string name="wifi_hotspot_auto_off_title">Turn off hotspot automatically</string>
     <!-- Summary for the toggle to turn off hotspot automatically [CHAR LIMIT=100]-->
     <string name="wifi_hotspot_auto_off_summary">Wi\u2011Fi hotspot will turn off if no devices are connected</string>
+    <!-- This string asks the user whether or not to allow an app to enable WiFi. [CHAR LIMIT=NONE] -->
+    <string name="wifi_ask_enable"><xliff:g id="requester" example="FancyApp">%s</xliff:g> wants to turn on Wi-Fi</string>
+    <!-- This string asks the user whether or not to allow an app to disable WiFi. [CHAR LIMIT=NONE] -->
+    <string name="wifi_ask_disable"><xliff:g id="requester" example="FancyApp">%s</xliff:g> wants to turn off Wi-Fi</string>
+    <!-- Summary text when Wi-Fi has error -->
+    <string name="wifi_error">Error</string>
 
     <!-- Bluetooth settings --><skip/>
     <!-- Title of Bluetooth settings pages. [CHAR LIMIT=30] -->
diff --git a/src/com/android/car/settings/wifi/WifiRequestToggleActivity.java b/src/com/android/car/settings/wifi/WifiRequestToggleActivity.java
new file mode 100644
index 0000000..0b79b4b
--- /dev/null
+++ b/src/com/android/car/settings/wifi/WifiRequestToggleActivity.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2019 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.car.settings.wifi;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+
+/**
+ * Code drop from {@link com.android.settings.wifi.RequestToggleWiFiActivity}.
+ *
+ * This {@link Activity} handles requests to toggle WiFi by collecting user
+ * consent and waiting until the state change is completed.
+ */
+public class WifiRequestToggleActivity extends Activity implements DialogInterface.OnClickListener,
+        DialogInterface.OnCancelListener {
+    private static final Logger LOG = new Logger(WifiRequestToggleActivity.class);
+    private static final long TOGGLE_TIMEOUT_MILLIS = 10000; // 10 sec
+
+    private static final int STATE_UNKNOWN = -1;
+    private static final int STATE_ENABLE = 1;
+    private static final int STATE_ENABLING = 2;
+    private static final int STATE_DISABLE = 3;
+    private static final int STATE_DISABLING = 4;
+
+    private final StateChangeReceiver mReceiver = new StateChangeReceiver();
+
+    private final Runnable mTimeoutCommand = () -> {
+        if (!isFinishing() && !isDestroyed()) {
+            finish();
+        }
+    };
+
+    @NonNull
+    private CarWifiManager mCarWifiManager;
+    @NonNull
+    private CharSequence mAppLabel;
+
+    private AlertDialog mDialog;
+    private int mAction = STATE_UNKNOWN;
+    private int mState = STATE_UNKNOWN;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mCarWifiManager = new CarWifiManager(getApplicationContext());
+
+        setResult(Activity.RESULT_CANCELED);
+
+        String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        if (TextUtils.isEmpty(packageName)) {
+            finish();
+            return;
+        }
+
+        try {
+            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
+                    packageName,  /* flags= */ 0);
+            mAppLabel = applicationInfo.loadSafeLabel(getPackageManager());
+        } catch (PackageManager.NameNotFoundException e) {
+            LOG.e("Couldn't find app with package name " + packageName);
+            finish();
+            return;
+        }
+
+        mAction = extractActionState();
+        if (mAction == STATE_UNKNOWN) {
+            finish();
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case DialogInterface.BUTTON_POSITIVE:
+                switch (mState) {
+                    case STATE_ENABLE:
+                        mCarWifiManager.setWifiEnabled(true);
+                        mState = STATE_ENABLING;
+                        updateUi();
+                        scheduleToggleTimeout();
+                        break;
+
+                    case STATE_DISABLE:
+                        mCarWifiManager.setWifiEnabled(false);
+                        mState = STATE_DISABLING;
+                        updateUi();
+                        scheduleToggleTimeout();
+                        break;
+                }
+                setResult(RESULT_OK);
+                break;
+            case DialogInterface.BUTTON_NEGATIVE:
+                finish();
+                break;
+        }
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        finish();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        mReceiver.register();
+
+        final int wifiState = mCarWifiManager.getWifiState();
+
+        switch (mAction) {
+            case STATE_ENABLE:
+                switch (wifiState) {
+                    case WifiManager.WIFI_STATE_ENABLED:
+                        setResult(RESULT_OK);
+                        finish();
+                        return;
+
+                    case WifiManager.WIFI_STATE_ENABLING:
+                        mState = STATE_ENABLING;
+                        scheduleToggleTimeout();
+                        break;
+                    default:
+                        mState = mAction;
+                }
+                break;
+
+            case STATE_DISABLE:
+                switch (wifiState) {
+                    case WifiManager.WIFI_STATE_DISABLED:
+                        setResult(RESULT_OK);
+                        finish();
+                        return;
+
+                    case WifiManager.WIFI_STATE_DISABLING:
+                        mState = STATE_DISABLING;
+                        scheduleToggleTimeout();
+                        break;
+                    default:
+                        mState = mAction;
+                }
+                break;
+        }
+
+        updateUi();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mReceiver.unregister();
+        unscheduleToggleTimeout();
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+    }
+
+    private void updateUi() {
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(/* context= */
+                this).setOnCancelListener(/* listener= */ this);
+        switch (mState) {
+            case STATE_ENABLE:
+                builder.setPositiveButton(R.string.allow, /* listener= */ this);
+                builder.setNegativeButton(R.string.deny, /* listener= */ this);
+                builder.setMessage(getString(R.string.wifi_ask_enable, mAppLabel));
+                break;
+
+            case STATE_ENABLING:
+                builder.setMessage(getString(R.string.wifi_starting, mAppLabel));
+                break;
+
+            case STATE_DISABLE:
+                builder.setPositiveButton(R.string.allow, /* listener= */ this);
+                builder.setNegativeButton(R.string.deny, /* listener= */ this);
+                builder.setMessage(getString(R.string.wifi_ask_disable, mAppLabel));
+                break;
+
+            case STATE_DISABLING:
+                builder.setMessage(getString(R.string.wifi_stopping, mAppLabel));
+                break;
+        }
+        mDialog = builder.create();
+        mDialog.show();
+    }
+
+    private void scheduleToggleTimeout() {
+        getWindow().getDecorView().postDelayed(mTimeoutCommand, TOGGLE_TIMEOUT_MILLIS);
+    }
+
+    private void unscheduleToggleTimeout() {
+        getWindow().getDecorView().removeCallbacks(mTimeoutCommand);
+    }
+
+    private int extractActionState() {
+        String action = getIntent().getAction();
+        switch (action) {
+            case WifiManager.ACTION_REQUEST_ENABLE:
+                return STATE_ENABLE;
+
+            case WifiManager.ACTION_REQUEST_DISABLE:
+                return STATE_DISABLE;
+
+            default:
+                return STATE_UNKNOWN;
+        }
+    }
+
+    private final class StateChangeReceiver extends BroadcastReceiver {
+        private final IntentFilter mFilter = new IntentFilter(
+                WifiManager.WIFI_STATE_CHANGED_ACTION);
+
+        public void register() {
+            registerReceiver(/* receiver= */ this, mFilter);
+        }
+
+        public void unregister() {
+            unregisterReceiver(/* receiver= */ this);
+        }
+
+        public void onReceive(Context context, Intent intent) {
+            Activity activity = WifiRequestToggleActivity.this;
+            if (activity.isFinishing() || activity.isDestroyed()) {
+                return;
+            }
+            int currentState = mCarWifiManager.getWifiState();
+            switch (currentState) {
+                case WifiManager.WIFI_STATE_ENABLED:
+                case WifiManager.WIFI_STATE_DISABLED:
+                    if (mState == STATE_ENABLING || mState == STATE_DISABLING) {
+                        activity.setResult(Activity.RESULT_OK);
+                        finish();
+                    }
+                    break;
+
+                case WifiManager.ERROR:
+                    Toast.makeText(activity, R.string.wifi_error, Toast.LENGTH_SHORT).show();
+                    finish();
+                    break;
+            }
+        }
+    }
+}