Home | History | Annotate | Download | only in vpn2
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.vpn2;
     18 
     19 import android.app.AlertDialog;
     20 import android.app.Dialog;
     21 import android.app.DialogFragment;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.net.ConnectivityManager;
     25 import android.net.IConnectivityManager;
     26 import android.os.Bundle;
     27 import android.os.RemoteException;
     28 import android.os.ServiceManager;
     29 import android.os.UserHandle;
     30 import android.security.Credentials;
     31 import android.security.KeyStore;
     32 import android.util.Log;
     33 import android.view.View;
     34 import android.widget.Toast;
     35 
     36 import com.android.internal.logging.nano.MetricsProto;
     37 import com.android.internal.net.LegacyVpnInfo;
     38 import com.android.internal.net.VpnConfig;
     39 import com.android.internal.net.VpnProfile;
     40 import com.android.settings.R;
     41 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
     42 
     43 /**
     44  * Fragment wrapper around a {@link ConfigDialog}.
     45  */
     46 public class ConfigDialogFragment extends InstrumentedDialogFragment implements
     47         DialogInterface.OnClickListener, DialogInterface.OnShowListener, View.OnClickListener,
     48         ConfirmLockdownFragment.ConfirmLockdownListener {
     49     private static final String TAG_CONFIG_DIALOG = "vpnconfigdialog";
     50     private static final String TAG = "ConfigDialogFragment";
     51 
     52     private static final String ARG_PROFILE = "profile";
     53     private static final String ARG_EDITING = "editing";
     54     private static final String ARG_EXISTS = "exists";
     55 
     56     private final IConnectivityManager mService = IConnectivityManager.Stub.asInterface(
     57             ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
     58     private Context mContext;
     59 
     60     private boolean mUnlocking = false;
     61 
     62 
     63     @Override
     64     public int getMetricsCategory() {
     65         return MetricsProto.MetricsEvent.DIALOG_LEGACY_VPN_CONFIG;
     66     }
     67 
     68     public static void show(VpnSettings parent, VpnProfile profile, boolean edit, boolean exists) {
     69         if (!parent.isAdded()) return;
     70 
     71         Bundle args = new Bundle();
     72         args.putParcelable(ARG_PROFILE, profile);
     73         args.putBoolean(ARG_EDITING, edit);
     74         args.putBoolean(ARG_EXISTS, exists);
     75 
     76         final ConfigDialogFragment frag = new ConfigDialogFragment();
     77         frag.setArguments(args);
     78         frag.setTargetFragment(parent, 0);
     79         frag.show(parent.getFragmentManager(), TAG_CONFIG_DIALOG);
     80     }
     81 
     82     @Override
     83     public void onAttach(final Context context) {
     84         super.onAttach(context);
     85         mContext = context;
     86     }
     87 
     88     @Override
     89     public void onResume() {
     90         super.onResume();
     91 
     92         // Check KeyStore here, so others do not need to deal with it.
     93         if (!KeyStore.getInstance().isUnlocked()) {
     94             if (!mUnlocking) {
     95                 // Let us unlock KeyStore. See you later!
     96                 Credentials.getInstance().unlock(mContext);
     97             } else {
     98                 // We already tried, but it is still not working!
     99                 dismiss();
    100             }
    101             mUnlocking = !mUnlocking;
    102             return;
    103         }
    104 
    105         // Now KeyStore is always unlocked. Reset the flag.
    106         mUnlocking = false;
    107     }
    108 
    109     @Override
    110     public Dialog onCreateDialog(Bundle savedInstanceState) {
    111         Bundle args = getArguments();
    112         VpnProfile profile = (VpnProfile) args.getParcelable(ARG_PROFILE);
    113         boolean editing = args.getBoolean(ARG_EDITING);
    114         boolean exists = args.getBoolean(ARG_EXISTS);
    115 
    116         final Dialog dialog = new ConfigDialog(getActivity(), this, profile, editing, exists);
    117         dialog.setOnShowListener(this);
    118         return dialog;
    119     }
    120 
    121     /**
    122      * Override for the default onClick handler which also calls dismiss().
    123      *
    124      * @see DialogInterface.OnClickListener#onClick(DialogInterface, int)
    125      */
    126     @Override
    127     public void onShow(DialogInterface dialogInterface) {
    128         ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
    129     }
    130 
    131     @Override
    132     public void onClick(View positiveButton) {
    133         onClick(getDialog(), AlertDialog.BUTTON_POSITIVE);
    134     }
    135 
    136     @Override
    137     public void onConfirmLockdown(Bundle options, boolean isAlwaysOn, boolean isLockdown) {
    138         VpnProfile profile = (VpnProfile) options.getParcelable(ARG_PROFILE);
    139         connect(profile, isAlwaysOn);
    140         dismiss();
    141     }
    142 
    143     @Override
    144     public void onClick(DialogInterface dialogInterface, int button) {
    145         ConfigDialog dialog = (ConfigDialog) getDialog();
    146         VpnProfile profile = dialog.getProfile();
    147 
    148         if (button == DialogInterface.BUTTON_POSITIVE) {
    149             // Possibly throw up a dialog to explain lockdown VPN.
    150             final boolean shouldLockdown = dialog.isVpnAlwaysOn();
    151             final boolean shouldConnect = shouldLockdown || !dialog.isEditing();
    152             final boolean wasLockdown = VpnUtils.isAnyLockdownActive(mContext);
    153             try {
    154                 final boolean replace = VpnUtils.isVpnActive(mContext);
    155                 if (shouldConnect && !isConnected(profile) &&
    156                         ConfirmLockdownFragment.shouldShow(replace, wasLockdown, shouldLockdown)) {
    157                     final Bundle opts = new Bundle();
    158                     opts.putParcelable(ARG_PROFILE, profile);
    159                     ConfirmLockdownFragment.show(this, replace, /* alwaysOn */ shouldLockdown,
    160                            /* from */  wasLockdown, /* to */ shouldLockdown, opts);
    161                 } else if (shouldConnect) {
    162                     connect(profile, shouldLockdown);
    163                 } else {
    164                     save(profile, false);
    165                 }
    166             } catch (RemoteException e) {
    167                 Log.w(TAG, "Failed to check active VPN state. Skipping.", e);
    168             }
    169         } else if (button == DialogInterface.BUTTON_NEUTRAL) {
    170             // Disable profile if connected
    171             if (!disconnect(profile)) {
    172                 Log.e(TAG, "Failed to disconnect VPN. Leaving profile in keystore.");
    173                 return;
    174             }
    175 
    176             // Delete from KeyStore
    177             KeyStore keyStore = KeyStore.getInstance();
    178             keyStore.delete(Credentials.VPN + profile.key, KeyStore.UID_SELF);
    179 
    180             updateLockdownVpn(false, profile);
    181         }
    182         dismiss();
    183     }
    184 
    185     @Override
    186     public void onCancel(DialogInterface dialog) {
    187         dismiss();
    188         super.onCancel(dialog);
    189     }
    190 
    191     private void updateLockdownVpn(boolean isVpnAlwaysOn, VpnProfile profile) {
    192         // Save lockdown vpn
    193         if (isVpnAlwaysOn) {
    194             // Show toast if vpn profile is not valid
    195             if (!profile.isValidLockdownProfile()) {
    196                 Toast.makeText(mContext, R.string.vpn_lockdown_config_error,
    197                         Toast.LENGTH_LONG).show();
    198                 return;
    199             }
    200 
    201             final ConnectivityManager conn = ConnectivityManager.from(mContext);
    202             conn.setAlwaysOnVpnPackageForUser(UserHandle.myUserId(), null,
    203                     /* lockdownEnabled */ false);
    204             VpnUtils.setLockdownVpn(mContext, profile.key);
    205         } else {
    206             // update only if lockdown vpn has been changed
    207             if (VpnUtils.isVpnLockdown(profile.key)) {
    208                 VpnUtils.clearLockdownVpn(mContext);
    209             }
    210         }
    211     }
    212 
    213     private void save(VpnProfile profile, boolean lockdown) {
    214         KeyStore.getInstance().put(Credentials.VPN + profile.key, profile.encode(),
    215                 KeyStore.UID_SELF, /* flags */ 0);
    216 
    217         // Flush out old version of profile
    218         disconnect(profile);
    219 
    220         // Notify lockdown VPN that the profile has changed.
    221         updateLockdownVpn(lockdown, profile);
    222     }
    223 
    224     private void connect(VpnProfile profile, boolean lockdown) {
    225         save(profile, lockdown);
    226 
    227         // Now try to start the VPN - this is not necessary if the profile is set as lockdown,
    228         // because just saving the profile in this mode will start a connection.
    229         if (!VpnUtils.isVpnLockdown(profile.key)) {
    230             VpnUtils.clearLockdownVpn(mContext);
    231             try {
    232                 mService.startLegacyVpn(profile);
    233             } catch (IllegalStateException e) {
    234                 Toast.makeText(mContext, R.string.vpn_no_network, Toast.LENGTH_LONG).show();
    235             } catch (RemoteException e) {
    236                 Log.e(TAG, "Failed to connect", e);
    237             }
    238         }
    239     }
    240 
    241     /**
    242      * Ensure that the VPN profile pointed at by {@param profile} is disconnected.
    243      *
    244      * @return {@code true} iff this VPN profile is no longer connected. Note that another profile
    245      *         may still be active - this function will then do nothing but still return success.
    246      */
    247     private boolean disconnect(VpnProfile profile) {
    248         try {
    249             if (!isConnected(profile)) {
    250                 return true;
    251             }
    252             return VpnUtils.disconnectLegacyVpn(getContext());
    253         } catch (RemoteException e) {
    254             Log.e(TAG, "Failed to disconnect", e);
    255             return false;
    256         }
    257     }
    258 
    259     private boolean isConnected(VpnProfile profile) throws RemoteException {
    260         LegacyVpnInfo connected = mService.getLegacyVpnInfo(UserHandle.myUserId());
    261         return connected != null && profile.key.equals(connected.key);
    262     }
    263 }
    264