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

Commit df21cc7d authored by Victor Chang's avatar Victor Chang Committed by Android (Google) Code Review
Browse files

Merge "Vpn settings per vpn" into nyc-dev

parents ad1b7f61 16da2aa4
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -5174,6 +5174,8 @@
    <string name="vpn_save">Save</string>
    <!-- Button label to connect to a VPN profile. [CHAR LIMIT=40] -->
    <string name="vpn_connect">Connect</string>
    <!-- Button label to continue changing a VPN profile. [CHAR LIMIT=40] -->
    <string name="vpn_continue">Continue</string>
    <!-- Dialog title to edit a VPN profile. [CHAR LIMIT=40] -->
    <string name="vpn_edit">Edit VPN profile</string>
    <!-- Button label to forget a VPN profile. [CHAR LIMIT=40] -->
@@ -5186,6 +5188,12 @@
    <string name="vpn_disconnect">Disconnect</string>
    <!-- Field label to show the version number for a VPN app. [CHAR LIMIT=40] -->
    <string name="vpn_version">Version <xliff:g id="version" example="3.3.0">%s</xliff:g></string>
    <!-- Button label to forget a VPN profile [CHAR LIMIT=40] -->
    <string name="vpn_forget_long">Forget VPN</string>
    <!-- Dialog message title to set another VPN app to be always-on [CHAR LIMIT=40] -->
    <string name="vpn_replace_always_on_vpn_title">Replace existing VPN?</string>
    <!-- Dialog message body to set another VPN app to be always-on [CHAR LIMIT=NONE] -->
    <string name="vpn_replace_always_on_vpn_message">You already have a VPN connected to this profile. If you connected to one, your existing VPN will be replaced.</string>
    <!-- Preference title for VPN settings. [CHAR LIMIT=40] -->
    <string name="vpn_title">VPN</string>
+38 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
        android:title="@string/vpn_title">

        <Preference
                android:key="version"
                android:textColor="?android:attr/textColorSecondary"
                android:selectable="false"/>

        <com.android.settingslib.RestrictedSwitchPreference
                android:key="always_on_vpn"
                android:title="@string/vpn_menu_lockdown"
                android:defaultValue="false"
                settings:userRestriction="no_config_vpn"/>

        <com.android.settings.DimmableIconPreference
                android:key="forget_vpn"
                android:title="@string/vpn_forget_long"
                android:icon="@drawable/ic_menu_delete"
                settings:userRestriction="no_config_vpn"
                settings:useAdminDisabledSummary="true" />
</PreferenceScreen>
+21 −10
Original line number Diff line number Diff line
@@ -521,22 +521,33 @@ public final class Utils extends com.android.settingslib.Utils {
    public static void startWithFragmentAsUser(Context context, String fragmentName, Bundle args,
            int titleResId, CharSequence title, boolean isShortcut,
            UserHandle userHandle) {
        // workaround to avoid crash in b/17523189
        if (userHandle.getIdentifier() == UserHandle.myUserId()) {
            startWithFragment(context, fragmentName, args, null, 0, titleResId, title, isShortcut);
        } else {
            Intent intent = onBuildStartFragmentIntent(context, fragmentName, args,
                    null /* titleResPackageName */, titleResId, title, isShortcut);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
            context.startActivityAsUser(intent, userHandle);
        }
    }

    public static void startWithFragmentAsUser(Context context, String fragmentName, Bundle args,
            String titleResPackageName, int titleResId, CharSequence title, boolean isShortcut,
            UserHandle userHandle) {
        Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResPackageName,
                titleResId, title, isShortcut);
        // workaround to avoid crash in b/17523189
        if (userHandle.getIdentifier() == UserHandle.myUserId()) {
            startWithFragment(context, fragmentName, args, null, 0, titleResPackageName, titleResId,
                    title, isShortcut);
        } else {
            Intent intent = onBuildStartFragmentIntent(context, fragmentName, args,
                    titleResPackageName, titleResId, title, isShortcut);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
            context.startActivityAsUser(intent, userHandle);
        }
    }

    /**
     * Build an Intent to launch a new activity showing the selected fragment.
+23 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.settings.vpn2;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
@@ -45,13 +46,25 @@ public class AppDialogFragment extends DialogFragment implements AppDialog.Liste
    private static final String ARG_PACKAGE = "package";

    private PackageInfo mPackageInfo;
    private Listener mListener;

    private final IConnectivityManager mService = IConnectivityManager.Stub.asInterface(
            ServiceManager.getService(Context.CONNECTIVITY_SERVICE));

    public static void show(VpnSettings parent, PackageInfo packageInfo, String label,
    public interface Listener {
        public void onForget();
        public void onCancel();
    }

    public static void show(Fragment parent, PackageInfo packageInfo, String label,
            boolean managing, boolean connected) {
        if (!parent.isAdded()) return;
        show(parent, null, packageInfo, label, managing, connected);
    }

    public static void show(Fragment parent, Listener listener, PackageInfo packageInfo,
            String label, boolean managing, boolean connected) {
        if (!parent.isAdded())
            return;

        Bundle args = new Bundle();
        args.putParcelable(ARG_PACKAGE, packageInfo);
@@ -60,6 +73,7 @@ public class AppDialogFragment extends DialogFragment implements AppDialog.Liste
        args.putBoolean(ARG_CONNECTED, connected);

        final AppDialogFragment frag = new AppDialogFragment();
        frag.mListener = listener;
        frag.setArguments(args);
        frag.setTargetFragment(parent, 0);
        frag.show(parent.getFragmentManager(), TAG_APP_DIALOG);
@@ -98,6 +112,9 @@ public class AppDialogFragment extends DialogFragment implements AppDialog.Liste
    @Override
    public void onCancel(DialogInterface dialog) {
        dismiss();
        if (mListener != null) {
            mListener.onCancel();
        }
        super.onCancel(dialog);
    }

@@ -111,6 +128,10 @@ public class AppDialogFragment extends DialogFragment implements AppDialog.Liste
            Log.e(TAG, "Failed to forget authorization of " + mPackageInfo.packageName +
                    " for user " + userId, e);
        }

        if (mListener != null) {
            mListener.onForget();
        }
    }

    private void onDisconnect(final DialogInterface dialog) {
+267 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.settings.vpn2;

import android.app.AlertDialog;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.support.v7.preference.Preference;
import android.util.Log;

import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.internal.net.VpnConfig;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.RestrictedPreference;
import com.android.settings.Utils;

import java.util.List;

import static android.app.AppOpsManager.OP_ACTIVATE_VPN;

public class AppManagementFragment extends SettingsPreferenceFragment
        implements Preference.OnPreferenceClickListener {

    private static final String TAG = "AppManagementFragment";

    private static final String ARG_PACKAGE_NAME = "package";

    private static final String KEY_VERSION = "version";
    private static final String KEY_ALWAYS_ON_VPN = "always_on_vpn";
    private static final String KEY_FORGET_VPN = "forget_vpn";

    private AppOpsManager mAppOpsManager;
    private PackageManager mPackageManager;
    private ConnectivityManager mConnectivityManager;

    // VPN app info
    private final int mUserId = UserHandle.myUserId();
    private int mPackageUid;
    private String mPackageName;
    private PackageInfo mPackageInfo;
    private String mVpnLabel;

    // UI preference
    private Preference mPreferenceVersion;
    private RestrictedSwitchPreference mPreferenceAlwaysOn;
    private RestrictedPreference mPreferenceForget;

    // Listener
    private final AppDialogFragment.Listener mForgetVpnDialogFragmentListener =
            new AppDialogFragment.Listener() {
        @Override
        public void onForget() {
            // Unset always-on-vpn when forgetting the VPN
            if (isVpnAlwaysOn()) {
                setAlwaysOnVpn(false);
            }
            // Also dismiss and go back to VPN list
            finish();
        }

        @Override
        public void onCancel() {
            // do nothing
        }
    };

    public static void show(Context context, AppPreference pref) {
        Bundle args = new Bundle();
        args.putString(ARG_PACKAGE_NAME, pref.getPackageName());
        Utils.startWithFragmentAsUser(context, AppManagementFragment.class.getName(), args, -1,
                pref.getLabel(), false, new UserHandle(pref.getUserId()));
    }

    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        addPreferencesFromResource(R.xml.vpn_app_management);

        mPackageManager = getContext().getPackageManager();
        mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
        mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);

        mPreferenceVersion = findPreference(KEY_VERSION);
        mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN);
        mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN);

        mPreferenceAlwaysOn.setOnPreferenceClickListener(this);
        mPreferenceForget.setOnPreferenceClickListener(this);
    }

    @Override
    public void onResume() {
        super.onResume();

        boolean isInfoLoaded = loadInfo();
        if (isInfoLoaded) {
            mPreferenceVersion.setTitle(
                    getPrefContext().getString(R.string.vpn_version, mPackageInfo.versionName));
            updateUI();
        } else {
            finish();
        }
    }

    @Override
    public boolean onPreferenceClick(Preference preference) {
        String key = preference.getKey();
        switch (key) {
            case KEY_FORGET_VPN:
                return onForgetVpnClick();
            case KEY_ALWAYS_ON_VPN:
                return onAlwaysOnVpnClick();
            default:
                Log.w(TAG, "unknown key is clicked: " + key);
                return false;
        }
    }

    @Override
    protected int getMetricsCategory() {
        return MetricsEvent.VPN;
    }

    private boolean onForgetVpnClick() {
        AppDialogFragment.show(this, mForgetVpnDialogFragmentListener, mPackageInfo, mVpnLabel,
                true /* editing */, true);
        return true;
    }

    private boolean onAlwaysOnVpnClick() {
        final boolean isChecked = mPreferenceAlwaysOn.isChecked();
        if (isChecked && isLegacyVpnLockDownOrAnotherPackageAlwaysOn()) {
            // Show dialog if user replace always-on-vpn package and show not checked first
            mPreferenceAlwaysOn.setChecked(false);
            ReplaceExistingVpnFragment.show(this);
        } else {
            setAlwaysOnVpn(isChecked);
        }
        return true;
    }

    private void setAlwaysOnVpn(boolean isEnabled) {
        // Only clear legacy lockdown vpn in system user.
        if (mUserId == UserHandle.USER_SYSTEM) {
            VpnUtils.clearLockdownVpn(getContext());
        }
        mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId, isEnabled ? mPackageName : null);
        updateUI();
    }

    private void updateUI() {
        if (isAdded()) {
            mPreferenceAlwaysOn.setChecked(isVpnAlwaysOn());
        }
    }

    private String getAlwaysOnVpnPackage() {
        return mConnectivityManager.getAlwaysOnVpnPackageForUser(mUserId);
    }

    private boolean isVpnAlwaysOn() {
        return mPackageName.equals(getAlwaysOnVpnPackage());
    }

    /**
     * @return false if the intent doesn't contain an existing package or can't retrieve activated
     * vpn info.
     */
    private boolean loadInfo() {
        final Bundle args = getArguments();
        if (args == null) {
            Log.e(TAG, "empty bundle");
            return false;
        }

        mPackageName = args.getString(ARG_PACKAGE_NAME);
        if (mPackageName == null) {
            Log.e(TAG, "empty package name");
            return false;
        }

        try {
            mPackageUid = mPackageManager.getPackageUid(mPackageName, /* PackageInfoFlags */ 0);
            mPackageInfo = mPackageManager.getPackageInfo(mPackageName, /* PackageInfoFlags */ 0);
            mVpnLabel = VpnConfig.getVpnLabel(getPrefContext(), mPackageName).toString();
        } catch (NameNotFoundException nnfe) {
            Log.e(TAG, "package not found", nnfe);
            return false;
        }

        if (!isVpnActivated()) {
            Log.e(TAG, "package didn't register VPN profile");
            return false;
        }

        return true;
    }

    private boolean isVpnActivated() {
        final List<AppOpsManager.PackageOps> apps = mAppOpsManager.getOpsForPackage(mPackageUid,
                mPackageName, new int[]{OP_ACTIVATE_VPN});
        return apps != null && apps.size() > 0 && apps.get(0) != null;
    }

    private boolean isLegacyVpnLockDownOrAnotherPackageAlwaysOn() {
        if (mUserId == UserHandle.USER_SYSTEM) {
            String lockdownKey = VpnUtils.getLockdownVpn();
            if (lockdownKey != null) {
                return true;
            }
        }

        return getAlwaysOnVpnPackage() != null && !isVpnAlwaysOn();
    }

    public static class ReplaceExistingVpnFragment extends DialogFragment
            implements DialogInterface.OnClickListener {

        public static void show(AppManagementFragment parent) {
            final ReplaceExistingVpnFragment frag = new ReplaceExistingVpnFragment();
            frag.setTargetFragment(parent, 0);
            frag.show(parent.getFragmentManager(), null);
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new AlertDialog.Builder(getActivity())
                    .setTitle(R.string.vpn_replace_always_on_vpn_title)
                    .setMessage(getActivity().getString(R.string.vpn_replace_always_on_vpn_message))
                    .setNegativeButton(getActivity().getString(R.string.vpn_cancel), null)
                    .setPositiveButton(getActivity().getString(R.string.vpn_continue), this)
                    .create();
        }

        @Override
        public void onClick(DialogInterface dialog, int which) {
            if (getTargetFragment() instanceof AppManagementFragment) {
                ((AppManagementFragment) getTargetFragment()).setAlwaysOnVpn(true);
            }
        }
    }
}
Loading