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

Commit e03c3c7e authored by Lorenzo Colitti's avatar Lorenzo Colitti
Browse files

Prompt if a network without an Internet connection is selected

When a network is explicitlySelected, keep it connected but do
not automatically switch to it. Instead, attempt to validate it,
and if 8 seconds have passed and the network is not yet
validated, prompt the user asking whether to switch to it anyway.

Bug: 20081183
Change-Id: I03a8459eb39979e3dc8e94662b85a44605dd7e69
parent e14627e4
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
@@ -285,6 +285,14 @@ public class ConnectivityManager {
     */
    public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal";

    /**
     * Action used to display a dialog that asks the user whether to connect to a network that is
     * not validated. This intent is used to start the dialog in settings via startActivity.
     *
     * @hide
     */
    public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED";

    /**
     * The absence of a connection type.
     * @hide
@@ -2454,6 +2462,29 @@ public class ConnectivityManager {
        } catch (RemoteException e) {}
    }

    /**
     * Informs the system whether it should switch to {@code network} regardless of whether it is
     * validated or not. If {@code accept} is true, and the network was explicitly selected by the
     * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become
     * the system default network regardless of any other network that's currently connected. If
     * {@code always} is true, then the choice is remembered, so that the next time the user
     * connects to this network, the system will switch to it.
     *
     * <p>This method requires the caller to hold the permission
     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
     *
     * @param network The network to accept.
     * @param accept Whether to accept the network even if unvalidated.
     * @param always Whether to remember this choice in the future.
     *
     * @hide
     */
    public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
        try {
            mService.setAcceptUnvalidated(network, accept, always);
        } catch (RemoteException e) {}
    }

    /**
     * Resets all connectivity manager settings back to factory defaults.
     * @hide
+2 −0
Original line number Diff line number Diff line
@@ -156,6 +156,8 @@ interface IConnectivityManager

    void releaseNetworkRequest(in NetworkRequest networkRequest);

    void setAcceptUnvalidated(in Network network, boolean accept, boolean always);

    int getRestoreDefaultNetworkDelay(int networkType);

    boolean addVpnAddress(String address, int prefixLength);
+35 −4
Original line number Diff line number Diff line
@@ -104,7 +104,7 @@ public abstract class NetworkAgent extends Handler {
    public static final int EVENT_UID_RANGES_REMOVED = BASE + 6;

    /**
     * Sent by ConnectivitySerice to the NetworkAgent to inform the agent of the
     * Sent by ConnectivityService to the NetworkAgent to inform the agent of the
     * networks status - whether we could use the network or could not, due to
     * either a bad network configuration (no internet link) or captive portal.
     *
@@ -119,9 +119,21 @@ public abstract class NetworkAgent extends Handler {
     * Sent by the NetworkAgent to ConnectivityService to indicate this network was
     * explicitly selected.  This should be sent before the NetworkInfo is marked
     * CONNECTED so it can be given special treatment at that time.
     *
     * obj = boolean indicating whether to use this network even if unvalidated
     */
    public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;

    /**
     * Sent by ConnectivityService to the NetworkAgent to inform the agent of
     * whether the network should in the future be used even if not validated.
     * This decision is made by the user, but it is the network transport's
     * responsibility to remember it.
     *
     * arg1 = 1 if true, 0 if false
     */
    public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;

    public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
            NetworkCapabilities nc, LinkProperties lp, int score) {
        this(looper, context, logTag, ni, nc, lp, score, null);
@@ -191,6 +203,9 @@ public abstract class NetworkAgent extends Handler {
                networkStatus(msg.arg1);
                break;
            }
            case CMD_SAVE_ACCEPT_UNVALIDATED: {
                saveAcceptUnvalidated(msg.arg1 != 0);
            }
        }
    }

@@ -258,10 +273,16 @@ public abstract class NetworkAgent extends Handler {
    /**
     * Called by the bearer to indicate this network was manually selected by the user.
     * This should be called before the NetworkInfo is marked CONNECTED so that this
     * Network can be given special treatment at that time.
     * Network can be given special treatment at that time. If {@code acceptUnvalidated} is
     * {@code true}, then the system will switch to this network. If it is {@code false} and the
     * network cannot be validated, the system will ask the user whether to switch to this network.
     * If the user confirms and selects "don't ask again", then the system will call
     * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
     * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
     * {@link #saveAcceptUnvalidated} to respect the user's choice.
     */
    public void explicitlySelected() {
        queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, 0);
    public void explicitlySelected(boolean acceptUnvalidated) {
        queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, acceptUnvalidated);
    }

    /**
@@ -290,6 +311,16 @@ public abstract class NetworkAgent extends Handler {
    protected void networkStatus(int status) {
    }

    /**
     * Called when the user asks to remember the choice to use this network even if unvalidated.
     * The transport is responsible for remembering the choice, and the next time the user connects
     * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
     * This method will only be called if {@link #explicitlySelected} was called with
     * {@code acceptUnvalidated} set to {@code false}.
     */
    protected void saveAcceptUnvalidated(boolean accept) {
    }

    protected void log(String s) {
        Log.d(LOG_TAG, "NetworkAgent: " + s);
    }
+10 −0
Original line number Diff line number Diff line
@@ -44,6 +44,13 @@ public class NetworkMisc implements Parcelable {
     */
    public boolean explicitlySelected;

    /**
     * Set if the user desires to use this network even if it is unvalidated. This field has meaning
     * only if {#link explicitlySelected} is true. If it is, this field must also be set to the
     * appropriate value based on previous user choice.
     */
    public boolean acceptUnvalidated;

    /**
     * For mobile networks, this is the subscriber ID (such as IMSI).
     */
@@ -56,6 +63,7 @@ public class NetworkMisc implements Parcelable {
        if (nm != null) {
            allowBypass = nm.allowBypass;
            explicitlySelected = nm.explicitlySelected;
            acceptUnvalidated = nm.acceptUnvalidated;
            subscriberId = nm.subscriberId;
        }
    }
@@ -69,6 +77,7 @@ public class NetworkMisc implements Parcelable {
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(allowBypass ? 1 : 0);
        out.writeInt(explicitlySelected ? 1 : 0);
        out.writeInt(acceptUnvalidated ? 1 : 0);
        out.writeString(subscriberId);
    }

@@ -78,6 +87,7 @@ public class NetworkMisc implements Parcelable {
            NetworkMisc networkMisc = new NetworkMisc();
            networkMisc.allowBypass = in.readInt() != 0;
            networkMisc.explicitlySelected = in.readInt() != 0;
            networkMisc.acceptUnvalidated = in.readInt() != 0;
            networkMisc.subscriberId = in.readString();
            return networkMisc;
        }
+115 −0
Original line number Diff line number Diff line
@@ -161,6 +161,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
    private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
            "android.telephony.apn-restore";

    // How long to wait before putting up a "This network doesn't have an Internet connection,
    // connect anyway?" dialog after the user selects a network that doesn't validate.
    private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;

    // How long to delay to removal of a pending intent based request.
    // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
    private final int mReleasePendingIntentDelayMs;
@@ -324,6 +328,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
     */
    private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27;

    /**
     * used to specify whether a network should be used even if unvalidated.
     * arg1 = whether to accept the network if it's unvalidated (1 or 0)
     * arg2 = whether to remember this choice in the future (1 or 0)
     * obj  = network
     */
    private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;

    /**
     * used to ask the user to confirm a connection to an unvalidated network.
     * obj  = network
     */
    private static final int EVENT_PROMPT_UNVALIDATED = 29;

    /** Handler used for internal events. */
    final private InternalHandler mHandler;
@@ -1843,6 +1860,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
                        loge("ERROR: created network explicitly selected.");
                    }
                    nai.networkMisc.explicitlySelected = true;
                    nai.networkMisc.acceptUnvalidated = (boolean) msg.obj;
                    break;
                }
                case NetworkMonitor.EVENT_NETWORK_TESTED: {
@@ -1866,6 +1884,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
                                android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS,
                                (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
                                0, null);

                        // TODO: trigger a NetworkCapabilities update so that the dialog can know
                        // that the network is now validated and close itself.
                    }
                    break;
                }
@@ -2227,6 +2248,91 @@ public class ConnectivityService extends IConnectivityManager.Stub
        }
    }

    public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
        enforceConnectivityInternalPermission();
        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
                accept ? 1 : 0, always ? 1: 0, network));
    }

    private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
        if (DBG) log("handleSetAcceptUnvalidated network=" + network +
                " accept=" + accept + " always=" + always);

        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
        if (nai == null) {
            // Nothing to do.
            return;
        }

        if (nai.everValidated) {
            // The network validated while the dialog box was up. Don't make any changes. There's a
            // TODO in the dialog code to make it go away if the network validates; once that's
            // implemented, taking action here will be confusing.
            return;
        }

        if (!nai.networkMisc.explicitlySelected) {
            Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
        }

        if (accept != nai.networkMisc.acceptUnvalidated) {
            int oldScore = nai.getCurrentScore();
            nai.networkMisc.acceptUnvalidated = accept;
            rematchAllNetworksAndRequests(nai, oldScore);
            sendUpdatedScoreToFactories(nai);
        }

        if (always) {
            nai.asyncChannel.sendMessage(
                    NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0);
        }

        // TODO: should we also disconnect from the network if accept is false?
    }

    private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
        mHandler.sendMessageDelayed(
                mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
                PROMPT_UNVALIDATED_DELAY_MS);
    }

    private void handlePromptUnvalidated(Network network) {
        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);

        // Only prompt if the network is unvalidated and was explicitly selected by the user, and if
        // we haven't already been told to switch to it regardless of whether it validated or not.
        if (nai == null || nai.everValidated ||
                !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
            return;
        }

        // TODO: What should we do if we've already switched to this network because we had no
        // better option? There are two obvious alternatives.
        //
        // 1. Decide that there's no point prompting because this is our only usable network.
        //    However, because we didn't prompt, if later on a validated network comes along, we'll
        //    either a) silently switch to it - bad if the user wanted to connect to stay on this
        //    unvalidated network - or b) prompt the user at that later time - bad because the user
        //    might not understand why they are now being prompted.
        //
        // 2. Always prompt the user, even if we have no other network to use. The user could then
        //    try to find an alternative network to join (remember, if we got here, then the user
        //    selected this network manually). This is bad because the prompt isn't really very
        //    useful.
        //
        // For now we do #1, but we can revisit that later.
        if (isDefaultNetwork(nai)) {
            return;
        }

        Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED);
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK, network);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setClassName("com.android.settings",
                "com.android.settings.wifi.WifiNoInternetDialog");
        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
    }

    private class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
@@ -2297,6 +2403,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
                    break;
                }
                case EVENT_SET_ACCEPT_UNVALIDATED: {
                    handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
                    break;
                }
                case EVENT_PROMPT_UNVALIDATED: {
                    handlePromptUnvalidated((Network) msg.obj);
                    break;
                }
                case EVENT_SYSTEM_READY: {
                    for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
                        nai.networkMonitor.systemReady = true;
@@ -4099,6 +4213,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
            notifyIfacesChanged();
            notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
            networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
            scheduleUnvalidatedPrompt(networkAgent);
            if (networkAgent.isVPN()) {
                // Temporarily disable the default proxy (not global).
                synchronized (mProxyLock) {
Loading