Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc662b18 authored by Roshan Pius's avatar Roshan Pius
Browse files

wifi(API): New API surface for network suggestion

Allows apps to provide the platform a list of wifi network credentials
that the device is allowed to connect. Each NetworkSuggestion instance
will hold a standard WifiConfiguration object to represent network
credentials along with some other meta info that will help the platform
make good network selection decisions. NetworkConfigBuilder will be
reused for building the NetworkSuggestion objects as well.
Apps also have a mechanism to register to be awakened via PendingIntent
when the platform connects to one of their suggestions. This mechanism
will require the app to
a) Hold location permission, and
b) Registered PendingIntent should hold a foreground service (to prevent
abuse of this mechanism to bypass platform background limit checks).

Bug: 115504887
Test: Unit tests
Test: `make api-stubs-docs-update-current-api`
Change-Id: I9f5223fa45d49c22ce8f81d0ba56e5d12565381d
parent 819e1a74
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -28753,6 +28753,7 @@ package android.net.wifi {
  public class WifiManager {
    method public int addNetwork(android.net.wifi.WifiConfiguration);
    method public boolean addNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>, android.app.PendingIntent);
    method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
    method public static int calculateSignalLevel(int, int);
    method public deprecated void cancelWps(android.net.wifi.WifiManager.WpsCallback);
@@ -28781,6 +28782,7 @@ package android.net.wifi {
    method public boolean reassociate();
    method public boolean reconnect();
    method public boolean removeNetwork(int);
    method public boolean removeNetworkSuggestions(java.util.List<android.net.wifi.WifiNetworkSuggestion>);
    method public void removePasspointConfiguration(java.lang.String);
    method public deprecated boolean saveConfiguration();
    method public void setTdlsEnabled(java.net.InetAddress, boolean);
@@ -28866,15 +28868,26 @@ package android.net.wifi {
  public class WifiNetworkConfigBuilder {
    ctor public WifiNetworkConfigBuilder();
    method public android.net.NetworkSpecifier buildNetworkSpecifier();
    method public android.net.wifi.WifiNetworkSuggestion buildNetworkSuggestion();
    method public android.net.wifi.WifiNetworkConfigBuilder setBssid(android.net.MacAddress);
    method public android.net.wifi.WifiNetworkConfigBuilder setBssidPattern(android.net.MacAddress, android.net.MacAddress);
    method public android.net.wifi.WifiNetworkConfigBuilder setEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig);
    method public android.net.wifi.WifiNetworkConfigBuilder setIsAppInteractionRequired();
    method public android.net.wifi.WifiNetworkConfigBuilder setIsHiddenSsid();
    method public android.net.wifi.WifiNetworkConfigBuilder setIsMetered();
    method public android.net.wifi.WifiNetworkConfigBuilder setIsUserInteractionRequired();
    method public android.net.wifi.WifiNetworkConfigBuilder setPriority(int);
    method public android.net.wifi.WifiNetworkConfigBuilder setPskPassphrase(java.lang.String);
    method public android.net.wifi.WifiNetworkConfigBuilder setSsid(java.lang.String);
    method public android.net.wifi.WifiNetworkConfigBuilder setSsidPattern(android.os.PatternMatcher);
  }
  public final class WifiNetworkSuggestion implements android.os.Parcelable {
    method public int describeContents();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.net.wifi.WifiNetworkSuggestion> CREATOR;
  }
  public deprecated class WpsInfo implements android.os.Parcelable {
    ctor public deprecated WpsInfo();
    ctor public deprecated WpsInfo(android.net.wifi.WpsInfo);
+60 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.net.ConnectivityManager;
@@ -1184,6 +1185,65 @@ public class WifiManager {
        }
    }

    /**
     * Provide a list of network suggestions to the device. See {@link WifiNetworkSuggestion}
     * for a detailed explanation of the parameters.
     *<p>
     * When the device decides to connect to one of the provided network suggestions, platform fires
     * the associated {@code pendingIntent} if
     * {@link WifiNetworkSuggestion#isAppInteractionRequired} is {@code true} and the
     * provided {@code pendingIntent} is non-null.
     *<p>
     * Registration of a non-null pending intent {@code pendingIntent} requires
     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
     *<p>
     * NOTE:
     * <li> These networks are just a suggestion to the platform. The platform will ultimately
     * decide on which network the device connects to. </li>
     * <li> When an app is uninstalled, all its suggested networks are discarded. If the device is
     * currently connected to a suggested network which is being removed then the device will
     * disconnect from that network.</li>
     * <li> No in-place modification of existing suggestions are allowed. Apps are expected to
     * remove suggestions using {@link #removeNetworkSuggestions(List)} and then add the modified
     * suggestion back using this API.</li>
     *
     * @param networkSuggestions List of network suggestions provided by the app.
     * @param pendingIntent Pending intent to be fired post connection for networks. These will be
     *                      fired only when connecting to a network which has the
     *                      {@link WifiNetworkSuggestion#isAppInteractionRequired} flag set.
     *                      Pending intent must hold a foreground service, else will be rejected.
     *                      (i.e {@link PendingIntent#isForegroundService()} should return true)
     * @return true on success, false if any of the suggestions match (See
     * {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app.
     * @throws {@link SecurityException} if the caller is missing required permissions.
     */
    public boolean addNetworkSuggestions(
            @NonNull List<WifiNetworkSuggestion> networkSuggestions,
            @Nullable PendingIntent pendingIntent) {
        // TODO(b/115504887): Implementation
        return false;
    }


    /**
     * Remove a subset of or all of networks from previously provided suggestions by the app to the
     * device.
     * See {@link WifiNetworkSuggestion} for a detailed explanation of the parameters.
     * See {@link WifiNetworkSuggestion#equals(Object)} for the equivalence evaluation used.
     *
     * @param networkSuggestions List of network suggestions to be removed. Pass an empty list
     *                           to remove all the previous suggestions provided by the app.
     * @return true on success, false if any of the suggestions do not match any suggestions
     * previously provided by the app. Any matching suggestions are removed from the device and
     * will not be considered for any further connection attempts.
     */
    public boolean removeNetworkSuggestions(
            @NonNull List<WifiNetworkSuggestion> networkSuggestions) {
        // TODO(b/115504887): Implementation
        return false;
    }

    /**
     * Add or update a Passpoint configuration.  The configuration provides a credential
     * for connecting to Passpoint networks that are operated by the Passpoint
+142 −3
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkNotNull;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.net.MacAddress;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
@@ -30,11 +31,14 @@ import android.util.Pair;

import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * WifiNetworkConfigBuilder to use for creating Wi-Fi network configuration.
 * <li>See {@link #buildNetworkSpecifier()} for creating a network specifier to use in
 * {@link NetworkRequest}.</li>
 * <li>See {@link #buildNetworkSuggestion()} for creating a network suggestion to use in
 * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)}.</li>
 */
public class WifiNetworkConfigBuilder {
    private static final String MATCH_ALL_SSID_PATTERN_PATH = ".*";
@@ -45,6 +49,7 @@ public class WifiNetworkConfigBuilder {
            new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS);
    private static final MacAddress MATCH_EXACT_BSSID_PATTERN_MASK =
            MacAddress.BROADCAST_ADDRESS;
    private static final int UNASSIGNED_PRIORITY = -1;

    /**
     * SSID pattern match specified by the app.
@@ -69,6 +74,23 @@ public class WifiNetworkConfigBuilder {
     * SSID-specific probe request must be used for scans.
     */
    private boolean mIsHiddenSSID;
    /**
     * Whether app needs to log in to captive portal to obtain Internet access.
     */
    private boolean mIsAppInteractionRequired;
    /**
     * Whether user needs to log in to captive portal to obtain Internet access.
     */
    private boolean mIsUserInteractionRequired;
    /**
     * Whether this network is metered or not.
     */
    private boolean mIsMetered;
    /**
     * Priority of this network among other network suggestions provided by the app.
     * The lower the number, the higher the priority (i.e value of 0 = highest priority).
     */
    private int mPriority;

    public WifiNetworkConfigBuilder() {
        mSsidPatternMatcher = null;
@@ -76,6 +98,10 @@ public class WifiNetworkConfigBuilder {
        mPskPassphrase = null;
        mEnterpriseConfig = null;
        mIsHiddenSSID = false;
        mIsAppInteractionRequired = false;
        mIsUserInteractionRequired = false;
        mIsMetered = false;
        mPriority = UNASSIGNED_PRIORITY;
    }

    /**
@@ -103,6 +129,8 @@ public class WifiNetworkConfigBuilder {
     * {@link #buildNetworkSpecifier}, sets the SSID to use for filtering networks from scan
     * results. Will only match networks whose SSID is identical to the UTF-8 encoding of the
     * specified value.</li>
     * <li>For network suggestions ({@link WifiNetworkSuggestion}), built using
     * {@link #buildNetworkSuggestion()}, sets the SSID for the network.</li>
     * <li>Overrides any previous value set using {@link #setSsid(String)} or
     * {@link #setSsidPattern(PatternMatcher)}.</li>
     *
@@ -195,7 +223,7 @@ public class WifiNetworkConfigBuilder {
    }

    /**
     * Whether this represents a hidden network.
     * Specifies whether this represents a hidden network.
     * <p>
     * <li>For network requests (see {@link NetworkSpecifier}), built using
     * {@link #buildNetworkSpecifier}, setting this disallows the usage of
@@ -211,6 +239,77 @@ public class WifiNetworkConfigBuilder {
        return this;
    }

    /**
     * Specifies whether the app needs to log in to a captive portal to obtain Internet access.
     * <p>
     * This will dictate if the associated pending intent in
     * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)} will be sent after
     * successfully connecting to the network.
     * Use this for captive portal type networks where the app needs to authenticate the user
     * before the device can access the network.
     * This setting will be ignored if the {@code PendingIntent} used to add this network
     * suggestion is null.
     * <p>
     * <li>Only allowed for creating network suggestion, i.e {@link #buildNetworkSuggestion()}.</li>
     * <li>If not set, defaults to false (i.e no app interaction required).</li>
     *
     * @return Instance of {@link WifiNetworkConfigBuilder} to enable chaining of the builder
     * method.
     */
    public WifiNetworkConfigBuilder setIsAppInteractionRequired() {
        mIsAppInteractionRequired = true;
        return this;
    }

    /**
     * Specifies whether the user needs to log in to a captive portal to obtain Internet access.
     * <p>
     * <li>Only allowed for creating network suggestion, i.e {@link #buildNetworkSuggestion()}.</li>
     * <li>If not set, defaults to false (i.e no user interaction required).</li>
     *
     * @return Instance of {@link WifiNetworkConfigBuilder} to enable chaining of the builder
     * method.
     */
    public WifiNetworkConfigBuilder setIsUserInteractionRequired() {
        mIsUserInteractionRequired = true;
        return this;
    }

    /**
     * Specify the priority of this network among other network suggestions provided by the same app
     * (priorities have no impact on suggestions by different apps). The lower the number, the
     * higher the priority (i.e value of 0 = highest priority).
     * <p>
     * <li>Only allowed for creating network suggestion, i.e {@link #buildNetworkSuggestion()}.</li>
     * <li>If not set, defaults to -1 (i.e unassigned priority).</li>
     *
     * @param priority Integer number representing the priority among suggestions by the app.
     * @return Instance of {@link WifiNetworkConfigBuilder} to enable chaining of the builder
     * method.
     * @throws IllegalArgumentException if the priority value is negative.
     */
    public WifiNetworkConfigBuilder setPriority(int priority) {
        if (priority < 0) {
            throw new IllegalArgumentException("Invalid priority value " + priority);
        }
        mPriority = priority;
        return this;
    }

    /**
     * Specifies whether this network is metered.
     * <p>
     * <li>Only allowed for creating network suggestion, i.e {@link #buildNetworkSuggestion()}.</li>
     * <li>If not set, defaults to false (i.e not metered).</li>
     *
     * @return Instance of {@link WifiNetworkConfigBuilder} to enable chaining of the builder
     * method.
     */
    public WifiNetworkConfigBuilder setIsMetered() {
        mIsMetered = true;
        return this;
    }

    /**
     * Set defaults for the various low level credential type fields in the newly created
     * WifiConfiguration object.
@@ -260,6 +359,10 @@ public class WifiNetworkConfigBuilder {
        }
        wifiConfiguration.enterpriseConfig = mEnterpriseConfig;
        wifiConfiguration.hiddenSSID = mIsHiddenSSID;
        wifiConfiguration.priority = mPriority;
        wifiConfiguration.meteredOverride =
                mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED
                           : WifiConfiguration.METERED_OVERRIDE_NONE;
        return wifiConfiguration;
    }

@@ -358,9 +461,15 @@ public class WifiNetworkConfigBuilder {
            throw new IllegalStateException("setSsid should also be invoked when "
                    + "setIsHiddenSsid is invoked for network specifier");
        }
        if (mIsAppInteractionRequired || mIsUserInteractionRequired
                || mPriority != -1 || mIsMetered) {
            throw new IllegalStateException("none of setIsAppInteractionRequired/"
                    + "setIsUserInteractionRequired/setPriority/setIsMetered are allowed for "
                    + "specifier");
        }
        if (!TextUtils.isEmpty(mPskPassphrase) && mEnterpriseConfig != null) {
            throw new IllegalStateException("only one of setPskPassphrase or setEnterpriseConfig "
                    + "can be invoked for network specifier");
            throw new IllegalStateException("only one of setPreSharedKey or setEnterpriseConfig can"
                    + " be invoked for network specifier");
        }

        return new WifiNetworkSpecifier(
@@ -369,4 +478,34 @@ public class WifiNetworkConfigBuilder {
                buildWifiConfiguration(),
                Process.myUid());
    }

    /**
     * Create a network suggestion object use in
     * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)}.
     * See {@link WifiNetworkSuggestion}.
     *
     * @return Instance of {@link WifiNetworkSuggestion}.
     * @throws IllegalStateException on invalid params set.
     */
    public WifiNetworkSuggestion buildNetworkSuggestion() {
        if (mSsidPatternMatcher == null) {
            throw new IllegalStateException("setSsid should be invoked for suggestion");
        }
        if (mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_LITERAL
                || mBssidPatternMatcher != null) {
            throw new IllegalStateException("none of setSsidPattern/setBssidPattern/setBssid are"
                    + " allowed for suggestion");
        }
        if (!TextUtils.isEmpty(mPskPassphrase) && mEnterpriseConfig != null) {
            throw new IllegalStateException("only one of setPreSharedKey or setEnterpriseConfig can"
                    + "be invoked for suggestion");
        }

        return new WifiNetworkSuggestion(
                buildWifiConfiguration(),
                mIsAppInteractionRequired,
                mIsUserInteractionRequired,
                Process.myUid());

    }
}
+143 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.
 */

package android.net.wifi;

import static com.android.internal.util.Preconditions.checkNotNull;

import android.app.PendingIntent;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.List;
import java.util.Objects;

/**
 * The Network Suggestion object is used to provide a Wi-Fi network for consideration when
 * auto-connecting to networks. Apps cannot directly create this object, they must use
 * {@link WifiNetworkConfigBuilder#buildNetworkSuggestion()} to obtain an instance
 * of this object.
 *<p>
 * Apps can provide a list of such networks to the platform using
 * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)}.
 */
public final class WifiNetworkSuggestion implements Parcelable {
    /**
     * Network configuration for the provided network.
     * @hide
     */
    public final WifiConfiguration wifiConfiguration;

    /**
     * Whether app needs to log in to captive portal to obtain Internet access.
     * This will dictate if the associated pending intent in
     * {@link WifiManager#addNetworkSuggestions(List, PendingIntent)} needs to be sent after
     * successfully connecting to the network.
     * @hide
     */
    public final boolean isAppInteractionRequired;

    /**
     * Whether user needs to log in to captive portal to obtain Internet access.
     * @hide
     */
    public final boolean isUserInteractionRequired;

    /**
     * The UID of the process initializing this network suggestion.
     * @hide
     */
    public final int suggestorUid;

    /** @hide */
    public WifiNetworkSuggestion(WifiConfiguration wifiConfiguration,
                                 boolean isAppInteractionRequired,
                                 boolean isUserInteractionRequired,
                                 int suggestorUid) {
        checkNotNull(wifiConfiguration);

        this.wifiConfiguration = wifiConfiguration;
        this.isAppInteractionRequired = isAppInteractionRequired;
        this.isUserInteractionRequired = isUserInteractionRequired;
        this.suggestorUid = suggestorUid;
    }

    public static final Creator<WifiNetworkSuggestion> CREATOR =
            new Creator<WifiNetworkSuggestion>() {
                @Override
                public WifiNetworkSuggestion createFromParcel(Parcel in) {
                    return new WifiNetworkSuggestion(
                            in.readParcelable(null), // wifiConfiguration
                            in.readBoolean(), // isAppInteractionRequired
                            in.readBoolean(), // isUserInteractionRequired
                            in.readInt() // suggestorUid
                    );
                }

                @Override
                public WifiNetworkSuggestion[] newArray(int size) {
                    return new WifiNetworkSuggestion[size];
                }
            };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(wifiConfiguration, flags);
        dest.writeBoolean(isAppInteractionRequired);
        dest.writeBoolean(isUserInteractionRequired);
        dest.writeInt(suggestorUid);
    }

    @Override
    public int hashCode() {
        return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.allowedKeyManagement,
                suggestorUid);
    }

    /**
     * Equals for network suggestions.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof WifiNetworkSuggestion)) {
            return false;
        }
        WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj;
        return Objects.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID)
                && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
                                  lhs.wifiConfiguration.allowedKeyManagement)
                && suggestorUid == lhs.suggestorUid;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("WifiNetworkSuggestion [")
                .append(", WifiConfiguration=").append(wifiConfiguration)
                .append(", isAppInteractionRequired=").append(isAppInteractionRequired)
                .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
                .append(", suggestorUid=").append(suggestorUid)
                .append("]");
        return sb.toString();
    }
}
+186 −5

File changed.

Preview size limit exceeded, changes collapsed.

Loading