Merge "Less intrusive VPN dialog and other UX tweaks." into lmp-dev

This commit is contained in:
Jeff Davidson
2014-08-20 23:59:29 +00:00
committed by Android (Google) Code Review
22 changed files with 174 additions and 517 deletions

View File

@@ -16,25 +16,25 @@
package android.app;
import android.Manifest;
import android.annotation.SystemApi;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.media.AudioAttributes.AttributeUsage;
import android.os.Binder;
import android.os.IBinder;
import android.os.UserManager;
import android.util.ArrayMap;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsCallback;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserManager;
import android.util.ArrayMap;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* API for interacting with "application operation" tracking.
@@ -203,8 +203,10 @@ public class AppOpsManager {
public static final int OP_TOAST_WINDOW = 45;
/** @hide Capture the device's display contents and/or audio */
public static final int OP_PROJECT_MEDIA = 46;
/** @hide Activate a VPN connection without user intervention. */
public static final int OP_ACTIVATE_VPN = 47;
/** @hide */
public static final int _NUM_OP = 47;
public static final int _NUM_OP = 48;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION =
@@ -218,6 +220,9 @@ public class AppOpsManager {
/** Continually monitoring location data with a relatively high power request. */
public static final String OPSTR_MONITOR_HIGH_POWER_LOCATION
= "android:monitor_location_high_power";
/** Activate a VPN connection without user intervention. @hide */
@SystemApi
public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
/**
* This maps each operation to the operation that serves as the
@@ -275,6 +280,7 @@ public class AppOpsManager {
OP_MUTE_MICROPHONE,
OP_TOAST_WINDOW,
OP_PROJECT_MEDIA,
OP_ACTIVATE_VPN,
};
/**
@@ -329,6 +335,7 @@ public class AppOpsManager {
null,
null,
null,
OPSTR_ACTIVATE_VPN,
};
/**
@@ -383,6 +390,7 @@ public class AppOpsManager {
"MUTE_MICROPHONE",
"TOAST_WINDOW",
"PROJECT_MEDIA",
"ACTIVATE_VPN",
};
/**
@@ -437,6 +445,7 @@ public class AppOpsManager {
null, // no permission for muting/unmuting microphone
null, // no permission for displaying toasts
null, // no permission for projecting media
null, // no permission for activating vpn
};
/**
@@ -492,6 +501,7 @@ public class AppOpsManager {
UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE
UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW
null, //PROJECT_MEDIA
UserManager.DISALLOW_CONFIG_VPN, // ACTIVATE_VPN
};
/**
@@ -546,6 +556,7 @@ public class AppOpsManager {
false, //MUTE_MICROPHONE
true, //TOAST_WINDOW
false, //PROJECT_MEDIA
false, //ACTIVATE_VPN
};
/**
@@ -599,6 +610,7 @@ public class AppOpsManager {
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA
AppOpsManager.MODE_IGNORED, // OP_ACTIVATE_VPN
};
/**
@@ -656,6 +668,7 @@ public class AppOpsManager {
false,
false,
false,
false,
};
private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>();

View File

@@ -108,6 +108,8 @@ interface IConnectivityManager
boolean prepareVpn(String oldPackage, String newPackage);
void setVpnPackageAuthorization(boolean authorized);
ParcelFileDescriptor establishVpn(in VpnConfig config);
VpnConfig getVpnConfig();

View File

@@ -40,7 +40,6 @@ public class LegacyVpnInfo implements Parcelable {
public String key;
public int state = -1;
public PendingIntent intent;
@Override
public int describeContents() {
@@ -51,7 +50,6 @@ public class LegacyVpnInfo implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeString(key);
out.writeInt(state);
out.writeParcelable(intent, flags);
}
public static final Parcelable.Creator<LegacyVpnInfo> CREATOR =
@@ -61,7 +59,6 @@ public class LegacyVpnInfo implements Parcelable {
LegacyVpnInfo info = new LegacyVpnInfo();
info.key = in.readString();
info.state = in.readInt();
info.intent = in.readParcelable(null);
return info;
}

View File

@@ -20,17 +20,19 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.net.LinkAddress;
import android.net.RouteInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.net.RouteInfo;
import android.net.LinkAddress;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.List;
import java.util.ArrayList;
import java.util.List;
/**
* A simple container used to carry information in VpnBuilder, VpnDialogs,
@@ -55,12 +57,19 @@ public class VpnConfig implements Parcelable {
return intent;
}
public static PendingIntent getIntentForStatusPanel(Context context) {
Intent intent = new Intent();
intent.setClassName(DIALOGS_PACKAGE, DIALOGS_PACKAGE + ".ManageDialog");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
return PendingIntent.getActivityAsUser(context, 0, intent, 0, null, UserHandle.CURRENT);
public static CharSequence getVpnLabel(Context context, String packageName)
throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
Intent intent = new Intent(SERVICE_INTERFACE);
intent.setPackage(packageName);
List<ResolveInfo> services = pm.queryIntentServices(intent, 0 /* flags */);
if (services != null && services.size() == 1) {
// This app contains exactly one VPN service. Call loadLabel, which will attempt to
// load the service's label, and fall back to the app label if none is present.
return services.get(0).loadLabel(pm);
} else {
return pm.getApplicationInfo(packageName, 0).loadLabel(pm);
}
}
public String user;

View File

@@ -834,8 +834,8 @@
<!-- Monitoring dialog title for normal devices [CHAR LIMIT=35]-->
<string name="monitoring_title">Network monitoring</string>
<!-- Monitoring dialog open app button [CHAR LIMIT=30] -->
<string name="open_app">Open app</string>
<!-- Monitoring dialog disable vpn button [CHAR LIMIT=30] -->
<string name="disable_vpn">Disable VPN</string>
<!-- Monitoring dialog disconnect vpn button [CHAR LIMIT=30] -->
<string name="disconnect_vpn">Disconnect VPN</string>

View File

@@ -119,11 +119,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
if (mSecurityController.isLegacyVpn()) {
mSecurityController.disconnectFromLegacyVpn();
} else {
mSecurityController.openVpnApp();
}
mSecurityController.disconnectFromVpn();
}
}
@@ -142,7 +138,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
if (mSecurityController.isLegacyVpn()) {
return mContext.getString(R.string.disconnect_vpn);
} else {
return mContext.getString(R.string.open_app);
return mContext.getString(R.string.disable_vpn);
}
}

View File

@@ -23,8 +23,7 @@ public interface SecurityController {
String getVpnApp();
boolean isLegacyVpn();
String getLegacyVpnName();
void openVpnApp();
void disconnectFromLegacyVpn();
void disconnectFromVpn();
void addCallback(VpnCallback callback);
void removeCallback(VpnCallback callback);

View File

@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.policy;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -109,18 +108,17 @@ public class SecurityControllerImpl implements SecurityController {
}
@Override
public void openVpnApp() {
Intent i = mContext.getPackageManager().getLaunchIntentForPackage(mVpnConfig.user);
if (i != null) {
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(i);
}
}
@Override
public void disconnectFromLegacyVpn() {
public void disconnectFromVpn() {
try {
mConnectivityService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
if (isLegacyVpn()) {
mConnectivityService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
} else {
// Prevent this app from initiating VPN connections in the future without user
// intervention.
mConnectivityService.setVpnPackageAuthorization(false);
mConnectivityService.prepareVpn(mVpnConfig.user, VpnConfig.LEGACY_VPN);
}
} catch (Exception e) {
Log.e(TAG, "Unable to disconnect from VPN", e);
}
@@ -154,9 +152,7 @@ public class SecurityControllerImpl implements SecurityController {
mIsVpnEnabled = mVpnConfig != null;
if (mVpnConfig != null && !mVpnConfig.legacy) {
ApplicationInfo info =
mContext.getPackageManager().getApplicationInfo(mVpnConfig.user, 0);
mVpnName = mContext.getPackageManager().getApplicationLabel(info).toString();
mVpnName = VpnConfig.getVpnLabel(mContext, mVpnConfig.user).toString();
}
} catch (RemoteException | NameNotFoundException e) {
Log.w(TAG, "Unable to get current VPN", e);

View File

@@ -28,14 +28,5 @@
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name=".ManageDialog"
android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -18,41 +18,12 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout android:layout_width="match_parent"
<TextView android:id="@+id/warning"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="3mm">
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView android:id="@+id/icon"
android:layout_width="@android:dimen/app_icon_size"
android:layout_height="@android:dimen/app_icon_size"
android:paddingRight="1mm"/>
<TextView android:id="@+id/prompt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="18sp"/>
</LinearLayout>
<TextView android:id="@+id/warning"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="1mm"
android:paddingBottom="1mm"
android:text="@string/warning"
android:textSize="18sp"/>
<CheckBox android:id="@+id/check"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/accept"
android:textSize="20sp"
android:filterTouchesWhenObscured="true"
android:checked="false"/>
</LinearLayout>
android:textSize="18sp"
android:paddingTop="4mm"
android:paddingLeft="3mm"
android:paddingRight="3mm"
android:paddingBottom="4mm"/>
</ScrollView>

View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3mm"
android:stretchColumns="0,1"
android:shrinkColumns="1">
<TableRow>
<TextView android:text="@string/session" style="@style/label"/>
<TextView android:id="@+id/session" style="@style/value"/>
</TableRow>
<TableRow>
<TextView android:text="@string/duration" style="@style/label"/>
<TextView android:id="@+id/duration" style="@style/value"/>
</TableRow>
<TableRow android:id="@+id/data_transmitted_row" android:visibility="gone">
<TextView android:text="@string/data_transmitted" style="@style/label"/>
<TextView android:id="@+id/data_transmitted" style="@style/value"/>
</TableRow>
<TableRow android:id="@+id/data_received_row" android:visibility="gone">
<TextView android:text="@string/data_received" style="@style/label"/>
<TextView android:id="@+id/data_received" style="@style/value"/>
</TableRow>
</TableLayout>

View File

@@ -17,40 +17,15 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Dialog title to identify the request from a VPN application. [CHAR LIMIT=60] -->
<string name="prompt"><xliff:g id="app">%s</xliff:g>
attempts to create a VPN connection.
</string>
<string name="prompt">Connection request</string>
<!-- Dialog message to warn about the risk of using a VPN application. [CHAR LIMIT=NONE] -->
<string name="warning">By proceeding, you are giving the application
permission to intercept all network traffic.
<b>Do NOT accept unless you trust the application.</b> Otherwise,
you run the risk of having your data compromised by a malicious
software.
</string>
<!-- Checkbox label to accept the request from a VPN application. [CHAR LIMIT=60] -->
<string name="accept">I trust this application.</string>
<!-- Dialog title for built-in VPN. [CHAR LIMIT=40] -->
<string name="legacy_title">VPN is connected</string>
<!-- Button label to configure the current VPN session. [CHAR LIMIT=20] -->
<string name="configure">Configure</string>
<!-- Button label to disconnect the current VPN session. [CHAR LIMIT=20] -->
<string name="disconnect">Disconnect</string>
<!-- Label for the name of the current VPN session. [CHAR LIMIT=20] -->
<string name="session">Session:</string>
<!-- Label for the duration of the current VPN session. [CHAR LIMIT=20] -->
<string name="duration">Duration:</string>
<!-- Label for the network usage of data transmitted over VPN. [CHAR LIMIT=20] -->
<string name="data_transmitted">Sent:</string>
<!-- Label for the network usage of data received over VPN. [CHAR LIMIT=20] -->
<string name="data_received">Received:</string>
<!-- Formatted string for the network usage over VPN. [CHAR LIMIT=40] -->
<string name="data_value_format">
<xliff:g id="number">%1$s</xliff:g> bytes /
<xliff:g id="number">%2$s</xliff:g> packets
<string name="warning"><xliff:g id="app">%s</xliff:g> wants to set up a VPN connection
that allows it to monitor network traffic. Only accept if you trust the source.
<![CDATA[
<br />
<br />
<img src="vpn_icon" />
]]> appears at the top of your screen when VPN is active.
</string>
</resources>

View File

@@ -18,21 +18,28 @@ package com.android.vpndialogs;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.net.IConnectivityManager;
import android.net.VpnService;
import android.os.ServiceManager;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.app.AlertActivity;
import com.android.internal.net.VpnConfig;
public class ConfirmDialog extends AlertActivity implements
CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener {
import java.util.List;
public class ConfirmDialog extends AlertActivity
implements DialogInterface.OnClickListener, ImageGetter {
private static final String TAG = "VpnConfirm";
private String mPackage;
@@ -56,27 +63,22 @@ public class ConfirmDialog extends AlertActivity implements
return;
}
PackageManager pm = getPackageManager();
ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
View view = View.inflate(this, R.layout.confirm, null);
((ImageView) view.findViewById(R.id.icon)).setImageDrawable(app.loadIcon(pm));
((TextView) view.findViewById(R.id.prompt)).setText(
getString(R.string.prompt, app.loadLabel(pm)));
((CompoundButton) view.findViewById(R.id.check)).setOnCheckedChangeListener(this);
mAlertParams.mIconAttrId = android.R.attr.alertDialogIcon;
mAlertParams.mTitle = getText(android.R.string.dialog_alert_title);
((TextView) view.findViewById(R.id.warning)).setText(
Html.fromHtml(
getString(R.string.warning, VpnConfig.getVpnLabel(this, mPackage)),
this, null /* tagHandler */));
mAlertParams.mTitle = getText(R.string.prompt);
mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
mAlertParams.mPositiveButtonListener = this;
mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
mAlertParams.mNegativeButtonListener = this;
mAlertParams.mView = view;
setupAlert();
getWindow().setCloseOnTouchOutside(false);
mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
mButton.setEnabled(false);
mButton.setFilterTouchesWhenObscured(true);
} catch (Exception e) {
Log.e(TAG, "onResume", e);
@@ -85,18 +87,24 @@ public class ConfirmDialog extends AlertActivity implements
}
@Override
public void onBackPressed() {
public Drawable getDrawable(String source) {
// Should only reach this when fetching the VPN icon for the warning string.
Drawable icon = getDrawable(R.drawable.ic_vpn_dialog);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
return icon;
}
@Override
public void onCheckedChanged(CompoundButton button, boolean checked) {
mButton.setEnabled(checked);
public void onBackPressed() {
}
@Override
public void onClick(DialogInterface dialog, int which) {
try {
if (which == DialogInterface.BUTTON_POSITIVE && mService.prepareVpn(null, mPackage)) {
if (mService.prepareVpn(null, mPackage)) {
// Authorize this app to initiate VPN connections in the future without user
// intervention.
mService.setVpnPackageAuthorization(true);
setResult(RESULT_OK);
}
} catch (Exception e) {

View File

@@ -1,204 +0,0 @@
/*
* Copyright (C) 2011 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.vpndialogs;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.IConnectivityManager;
import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.android.internal.app.AlertActivity;
import com.android.internal.net.VpnConfig;
import java.io.DataInputStream;
import java.io.FileInputStream;
public class ManageDialog extends AlertActivity implements
DialogInterface.OnClickListener, Handler.Callback {
private static final String TAG = "VpnManage";
private VpnConfig mConfig;
private IConnectivityManager mService;
private TextView mDuration;
private TextView mDataTransmitted;
private TextView mDataReceived;
private boolean mDataRowsHidden;
private Handler mHandler;
@Override
protected void onResume() {
super.onResume();
if (getCallingPackage() != null) {
Log.e(TAG, getCallingPackage() + " cannot start this activity");
finish();
return;
}
try {
mService = IConnectivityManager.Stub.asInterface(
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
mConfig = mService.getVpnConfig();
// mConfig can be null if we are a restricted user, in that case don't show this dialog
if (mConfig == null) {
finish();
return;
}
View view = View.inflate(this, R.layout.manage, null);
if (mConfig.session != null) {
((TextView) view.findViewById(R.id.session)).setText(mConfig.session);
}
mDuration = (TextView) view.findViewById(R.id.duration);
mDataTransmitted = (TextView) view.findViewById(R.id.data_transmitted);
mDataReceived = (TextView) view.findViewById(R.id.data_received);
mDataRowsHidden = true;
if (mConfig.legacy) {
mAlertParams.mIconId = android.R.drawable.ic_dialog_info;
mAlertParams.mTitle = getText(R.string.legacy_title);
} else {
PackageManager pm = getPackageManager();
ApplicationInfo app = pm.getApplicationInfo(mConfig.user, 0);
mAlertParams.mIcon = app.loadIcon(pm);
mAlertParams.mTitle = app.loadLabel(pm);
}
if (mConfig.configureIntent != null) {
mAlertParams.mPositiveButtonText = getText(R.string.configure);
mAlertParams.mPositiveButtonListener = this;
}
mAlertParams.mNeutralButtonText = getText(R.string.disconnect);
mAlertParams.mNeutralButtonListener = this;
mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
mAlertParams.mNegativeButtonListener = this;
mAlertParams.mView = view;
setupAlert();
if (mHandler == null) {
mHandler = new Handler(this);
}
mHandler.sendEmptyMessage(0);
} catch (Exception e) {
Log.e(TAG, "onResume", e);
finish();
}
}
@Override
protected void onPause() {
super.onPause();
if (!isFinishing()) {
finish();
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
try {
if (which == DialogInterface.BUTTON_POSITIVE) {
mConfig.configureIntent.send();
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
if (mConfig.legacy) {
mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
} else {
mService.prepareVpn(mConfig.user, VpnConfig.LEGACY_VPN);
}
}
} catch (Exception e) {
Log.e(TAG, "onClick", e);
finish();
}
}
@Override
public boolean handleMessage(Message message) {
mHandler.removeMessages(0);
if (!isFinishing()) {
if (mConfig.startTime != -1) {
long seconds = (SystemClock.elapsedRealtime() - mConfig.startTime) / 1000;
mDuration.setText(String.format("%02d:%02d:%02d",
seconds / 3600, seconds / 60 % 60, seconds % 60));
}
String[] numbers = getNumbers();
if (numbers != null) {
// First unhide the related data rows.
if (mDataRowsHidden) {
findViewById(R.id.data_transmitted_row).setVisibility(View.VISIBLE);
findViewById(R.id.data_received_row).setVisibility(View.VISIBLE);
mDataRowsHidden = false;
}
// [1] and [2] are received data in bytes and packets.
mDataReceived.setText(getString(R.string.data_value_format,
numbers[1], numbers[2]));
// [9] and [10] are transmitted data in bytes and packets.
mDataTransmitted.setText(getString(R.string.data_value_format,
numbers[9], numbers[10]));
}
mHandler.sendEmptyMessageDelayed(0, 1000);
}
return true;
}
private String[] getNumbers() {
DataInputStream in = null;
try {
// See dev_seq_printf_stats() in net/core/dev.c.
in = new DataInputStream(new FileInputStream("/proc/net/dev"));
String prefix = mConfig.interfaze + ':';
while (true) {
String line = in.readLine().trim();
if (line.startsWith(prefix)) {
String[] numbers = line.substring(prefix.length()).split(" +");
for (int i = 1; i < 17; ++i) {
if (!numbers[i].equals("0")) {
return numbers;
}
}
break;
}
}
} catch (Exception e) {
// ignore
} finally {
try {
in.close();
} catch (Exception e) {
// ignore
}
}
return null;
}
}

View File

@@ -2662,6 +2662,20 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
/**
* Set whether the current VPN package has the ability to launch VPNs without
* user intervention. This method is used by system UIs and not available
* in ConnectivityManager. Permissions are checked in Vpn class.
* @hide
*/
@Override
public void setVpnPackageAuthorization(boolean authorized) {
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized(mVpns) {
mVpns.get(user).setPackageAuthorization(authorized);
}
}
/**
* Configure a TUN interface and return its file descriptor. Parameters
* are encoded and opaque to this class. This method is used by VpnBuilder

View File

@@ -21,9 +21,7 @@ import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import android.app.AppGlobals;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -35,10 +33,6 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.BaseNetworkStateTracker;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
@@ -51,7 +45,6 @@ import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkMisc;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.UidRange;
import android.os.Binder;
@@ -70,15 +63,15 @@ import android.os.UserManager;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.Log;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.R;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.server.net.BaseNetworkObserver;
import libcore.io.IoUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -92,8 +85,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import libcore.io.IoUtils;
/**
* @hide
*/
@@ -114,8 +105,6 @@ public class Vpn {
private boolean mAllowIPv6;
private Connection mConnection;
private LegacyVpnRunner mLegacyVpnRunner;
private PendingIntent mStatusIntent;
private volatile boolean mEnableNotif = true;
private volatile boolean mEnableTeardown = true;
private final IConnectivityManager mConnService;
private final INetworkManagementService mNetd;
@@ -179,14 +168,6 @@ public class Vpn {
mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
}
/**
* Set if this object is responsible for showing its own notifications. When
* {@code false}, notifications are handled externally by someone else.
*/
public void setEnableNotifications(boolean enableNotif) {
mEnableNotif = enableNotif;
}
/**
* Set if this object is responsible for watching for {@link NetworkInfo}
* teardown. When {@code false}, teardown is handled externally by someone
@@ -228,6 +209,20 @@ public class Vpn {
public synchronized boolean prepare(String oldPackage, String newPackage) {
// Return false if the package does not match.
if (oldPackage != null && !oldPackage.equals(mPackage)) {
// The package doesn't match. If this VPN was not previously authorized, return false
// to force user authorization. Otherwise, revoke the VPN anyway.
if (!oldPackage.equals(VpnConfig.LEGACY_VPN) && isVpnUserPreConsented(oldPackage)) {
long token = Binder.clearCallingIdentity();
try {
// This looks bizarre, but it is what ConfirmDialog in VpnDialogs is doing when
// the user clicks through to allow the VPN to consent. So we are emulating the
// action of the dialog without actually showing it.
prepare(null, oldPackage);
} finally {
Binder.restoreCallingIdentity(token);
}
return true;
}
return false;
}
@@ -240,11 +235,8 @@ public class Vpn {
// Check if the caller is authorized.
enforceControlPermission();
// Reset the interface and hide the notification.
// Reset the interface.
if (mInterface != null) {
for (UidRange uidRange : mVpnUsers) {
hideNotification(uidRange.getStartUser());
}
agentDisconnect();
jniReset(mInterface);
mInterface = null;
@@ -287,12 +279,46 @@ public class Vpn {
Binder.restoreCallingIdentity(token);
}
mConfig = null;
updateState(DetailedState.IDLE, "prepare");
return true;
}
/**
* Set whether the current package has the ability to launch VPNs without user intervention.
*/
public void setPackageAuthorization(boolean authorized) {
// Check if the caller is authorized.
enforceControlPermission();
if (mPackage == null || VpnConfig.LEGACY_VPN.equals(mPackage)) {
return;
}
long token = Binder.clearCallingIdentity();
try {
AppOpsManager appOps =
(AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
appOps.setMode(AppOpsManager.OP_ACTIVATE_VPN, mOwnerUID, mPackage,
authorized ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
} catch (Exception e) {
Log.wtf(TAG, "Failed to set app ops for package " + mPackage, e);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private boolean isVpnUserPreConsented(String packageName) {
AppOpsManager appOps =
(AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
// Verify that the caller matches the given package and has permission to activate VPNs.
return appOps.noteOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, Binder.getCallingUid(),
packageName) == AppOpsManager.MODE_ALLOWED;
}
private int getAppUid(String app) {
if (app == VpnConfig.LEGACY_VPN) {
if (VpnConfig.LEGACY_VPN.equals(app)) {
return Process.myUid();
}
PackageManager pm = mContext.getPackageManager();
@@ -355,9 +381,10 @@ public class Vpn {
try {
mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE,
mNetworkInfo, mNetworkCapabilities, lp, 0, networkMisc) {
@Override
public void unwanted() {
// We are user controlled, not driven by NetworkRequest.
};
}
};
} finally {
Binder.restoreCallingIdentity(token);
@@ -540,39 +567,6 @@ public class Vpn {
// add the user
mVpnUsers.add(UidRange.createForUser(user));
// show the notification
if (!mPackage.equals(VpnConfig.LEGACY_VPN)) {
// Load everything for the user's notification
PackageManager pm = mContext.getPackageManager();
ApplicationInfo app = null;
final long token = Binder.clearCallingIdentity();
try {
app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId);
} catch (RemoteException e) {
throw new IllegalStateException("Invalid application");
} finally {
Binder.restoreCallingIdentity(token);
}
String label = app.loadLabel(pm).toString();
// Load the icon and convert it into a bitmap.
Drawable icon = app.loadIcon(pm);
Bitmap bitmap = null;
if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
int width = mContext.getResources().getDimensionPixelSize(
android.R.dimen.notification_large_icon_width);
int height = mContext.getResources().getDimensionPixelSize(
android.R.dimen.notification_large_icon_height);
icon.setBounds(0, 0, width, height);
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
icon.draw(c);
c.setBitmap(null);
}
showNotification(label, bitmap, user);
} else {
showNotification(null, null, user);
}
}
private void removeVpnUserLocked(int user) {
@@ -584,7 +578,6 @@ public class Vpn {
mNetworkAgent.removeUidRanges(new UidRange[] { uidRange });
}
mVpnUsers.remove(uidRange);
hideNotification(user);
}
private void onUserAdded(int userId) {
@@ -652,9 +645,6 @@ public class Vpn {
public void interfaceRemoved(String interfaze) {
synchronized (Vpn.this) {
if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
for (UidRange uidRange : mVpnUsers) {
hideNotification(uidRange.getStartUser());
}
mVpnUsers = null;
mInterface = null;
if (mConnection != null) {
@@ -712,56 +702,6 @@ public class Vpn {
}
}
private void showNotification(String label, Bitmap icon, int user) {
if (!mEnableNotif) return;
final long token = Binder.clearCallingIdentity();
try {
mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext);
NotificationManager nm = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (nm != null) {
String title = (label == null) ? mContext.getString(R.string.vpn_title) :
mContext.getString(R.string.vpn_title_long, label);
String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) :
mContext.getString(R.string.vpn_text_long, mConfig.session);
Notification notification = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.vpn_connected)
.setLargeIcon(icon)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(mStatusIntent)
.setDefaults(0)
.setOngoing(true)
.setColor(mContext.getResources().getColor(
com.android.internal.R.color.system_notification_accent_color))
.build();
nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user));
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void hideNotification(int user) {
if (!mEnableNotif) return;
mStatusIntent = null;
NotificationManager nm = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (nm != null) {
final long token = Binder.clearCallingIdentity();
try {
nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user));
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
public synchronized boolean addAddress(String address, int prefixLength) {
if (Binder.getCallingUid() != mOwnerUID || mInterface == null || mNetworkAgent == null) {
return false;
@@ -971,9 +911,6 @@ public class Vpn {
final LegacyVpnInfo info = new LegacyVpnInfo();
info.key = mConfig.user;
info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo);
if (mNetworkInfo.isConnected()) {
info.intent = mStatusIntent;
}
return info;
}

View File

@@ -205,7 +205,6 @@ public class LockdownVpnTracker {
private void initLocked() {
Slog.d(TAG, "initLocked()");
mVpn.setEnableNotifications(false);
mVpn.setEnableTeardown(false);
final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
@@ -249,7 +248,6 @@ public class LockdownVpnTracker {
hideNotification();
mContext.unregisterReceiver(mResetReceiver);
mVpn.setEnableNotifications(true);
mVpn.setEnableTeardown(true);
}