diff options
8 files changed, 124 insertions, 9 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ce10bad7a207..39f415e6ad8f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6939,11 +6939,13 @@ public class DevicePolicyManager { /** * Return whether network logging is enabled by a device owner. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only + * be {@code null} if the caller has MANAGE_USERS permission. * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise. - * @throws {@link SecurityException} if {@code admin} is not a device owner. + * @throws {@link SecurityException} if {@code admin} is not a device owner and caller has + * no MANAGE_USERS permission */ - public boolean isNetworkLoggingEnabled(@NonNull ComponentName admin) { + public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) { throwIfParentInstance("isNetworkLoggingEnabled"); try { return mService.isNetworkLoggingEnabled(admin); diff --git a/packages/SystemUI/res/drawable/ic_qs_network_logging.xml b/packages/SystemUI/res/drawable/ic_qs_network_logging.xml new file mode 100644 index 000000000000..1340ae161729 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_network_logging.xml @@ -0,0 +1,29 @@ +<!-- +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. +--> + +<!-- Placeholder icon for network logging until the real icon is finalized--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="12.0dp" + android:height="12.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="#4DFFFFFF" > + <path + android:fillColor="#FFFFFFFF" + android:pathData="M7,18v-2h6v2H7z M7,14v-2h10v2H7z M8.5,9 12,5.5 15.5,9 13,9 13,13 11,13 11,9z"/> + +</vector> diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml index 53baf74ffee9..8667a5a8f5cd 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer.xml @@ -39,4 +39,16 @@ android:src="@drawable/ic_qs_vpn" android:visibility="invisible" /> -</RelativeLayout>
\ No newline at end of file + <!-- Only shown if both images are visible --> + <ImageView + android:id="@+id/footer_icon2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginEnd="8dp" + android:layout_toStartOf="@id/footer_icon" + android:contentDescription="@null" + android:src="@drawable/ic_qs_network_logging" + android:visibility="invisible" /> + +</RelativeLayout> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index 43308def0f4a..f3da47b1ba82 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -51,6 +51,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene private final View mRootView; private final TextView mFooterText; private final ImageView mFooterIcon; + private final ImageView mFooterIcon2; private final Context mContext; private final Callback mCallback = new Callback(); @@ -62,8 +63,11 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene private boolean mIsVisible; private boolean mIsIconVisible; + private boolean mIsIcon2Visible; private CharSequence mFooterTextContent = null; + private int mFooterTextId; private int mFooterIconId; + private int mFooterIcon2Id; public QSFooter(QSPanel qsPanel, Context context) { mRootView = LayoutInflater.from(context) @@ -71,7 +75,9 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene mRootView.setOnClickListener(this); mFooterText = (TextView) mRootView.findViewById(R.id.footer_text); mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon); + mFooterIcon2 = (ImageView) mRootView.findViewById(R.id.footer_icon2); mFooterIconId = R.drawable.ic_qs_vpn; + mFooterIcon2Id = R.drawable.ic_qs_network_logging; mContext = context; mMainHandler = new Handler(Looper.getMainLooper()); } @@ -119,7 +125,10 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } private void handleRefreshState() { - mIsIconVisible = mSecurityController.isVpnEnabled(); + boolean isVpnEnabled = mSecurityController.isVpnEnabled(); + boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled(); + mIsIconVisible = isVpnEnabled || isNetworkLoggingEnabled; + mIsIcon2Visible = isVpnEnabled && isNetworkLoggingEnabled; if (mSecurityController.isDeviceManaged()) { final CharSequence organizationName = mSecurityController.getDeviceOwnerOrganizationName(); @@ -131,12 +140,21 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene mContext.getResources().getString(R.string.do_disclosure_generic); } mIsVisible = true; + int footerIconId = isVpnEnabled + ? R.drawable.ic_qs_vpn + : R.drawable.ic_qs_network_logging; + if (mFooterIconId != footerIconId) { + mFooterIconId = footerIconId; + mMainHandler.post(mUpdateIcon); + } } else { boolean isBranded = mSecurityController.isVpnBranded(); mFooterTextContent = mContext.getResources().getText( isBranded ? R.string.branded_vpn_footer : R.string.vpn_footer); // Update the VPN footer icon, if needed. - int footerIconId = isBranded ? R.drawable.ic_qs_branded_vpn : R.drawable.ic_qs_vpn; + int footerIconId = isVpnEnabled + ? (isBranded ? R.drawable.ic_qs_branded_vpn : R.drawable.ic_qs_vpn) + : R.drawable.ic_qs_network_logging; if (mFooterIconId != footerIconId) { mFooterIconId = footerIconId; mMainHandler.post(mUpdateIcon); @@ -258,6 +276,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene @Override public void run() { mFooterIcon.setImageResource(mFooterIconId); + mFooterIcon2.setImageResource(mFooterIcon2Id); } }; @@ -269,6 +288,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } mRootView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE); mFooterIcon.setVisibility(mIsIconVisible ? View.VISIBLE : View.INVISIBLE); + mFooterIcon2.setVisibility(mIsIcon2Visible ? View.VISIBLE : View.INVISIBLE); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java index 69281b52142c..3142228551b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java @@ -24,6 +24,7 @@ public interface SecurityController extends CallbackController<SecurityControlle String getDeviceOwnerName(); String getProfileOwnerName(); CharSequence getDeviceOwnerOrganizationName(); + boolean isNetworkLoggingEnabled(); boolean isVpnEnabled(); boolean isVpnRestricted(); /** Whether the VPN app should use branded VPN iconography. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 142f21b6f847..df959bd722ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -159,6 +159,11 @@ public class SecurityControllerImpl implements SecurityController { } @Override + public boolean isNetworkLoggingEnabled() { + return mDevicePolicyManager.isNetworkLoggingEnabled(null); + } + + @Override public boolean isVpnEnabled() { for (int profileId : mUserManager.getProfileIdsWithDisabled(mVpnUserId)) { if (mCurrentVpns.get(profileId) != null) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java index 1987009f69de..4c25c62e82a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java @@ -35,9 +35,11 @@ import org.junit.runner.RunWith; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -53,6 +55,8 @@ public class QSFooterTest extends SysuiTestCase { private ViewGroup mRootView = mock(ViewGroup.class); private TextView mFooterText = mock(TextView.class); + private ImageView mFooterIcon = mock(ImageView.class); + private ImageView mFooterIcon2 = mock(ImageView.class); private QSFooter mFooter; private Resources mResources; private SecurityController mSecurityController = mock(SecurityController.class); @@ -60,7 +64,8 @@ public class QSFooterTest extends SysuiTestCase { @Before public void setUp() { when(mRootView.findViewById(R.id.footer_text)).thenReturn(mFooterText); - when(mRootView.findViewById(R.id.footer_icon)).thenReturn(mock(ImageView.class)); + when(mRootView.findViewById(R.id.footer_icon)).thenReturn(mFooterIcon); + when(mRootView.findViewById(R.id.footer_icon2)).thenReturn(mFooterIcon2); final LayoutInflater layoutInflater = mock(LayoutInflater.class); when(layoutInflater.inflate(eq(R.layout.quick_settings_footer), anyObject(), anyBoolean())) .thenReturn(mRootView); @@ -114,6 +119,48 @@ public class QSFooterTest extends SysuiTestCase { } @Test + public void testNetworkLoggingEnabled() { + when(mSecurityController.isDeviceManaged()).thenReturn(true); + when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); + when(mSecurityController.isVpnEnabled()).thenReturn(false); + mFooter.refreshState(); + + waitForIdleSync(mFooter.mHandler); + verify(mFooterIcon).setVisibility(View.VISIBLE); + verify(mFooterIcon).setImageResource(R.drawable.ic_qs_network_logging); + verify(mFooterIcon2).setVisibility(View.INVISIBLE); + } + + @Test + public void testVpnEnabled() { + when(mSecurityController.isDeviceManaged()).thenReturn(true); + when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(false); + when(mSecurityController.isVpnEnabled()).thenReturn(true); + when(mSecurityController.isVpnBranded()).thenReturn(false); + mFooter.refreshState(); + + waitForIdleSync(mFooter.mHandler); + verify(mFooterIcon).setVisibility(View.VISIBLE); + verify(mFooterIcon, never()).setImageResource(anyInt()); + verify(mFooterIcon2).setVisibility(View.INVISIBLE); + } + + @Test + public void testNetworkLoggingAndVpnEnabled() { + when(mSecurityController.isDeviceManaged()).thenReturn(true); + when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); + when(mSecurityController.isVpnEnabled()).thenReturn(true); + when(mSecurityController.isVpnBranded()).thenReturn(false); + mFooter.refreshState(); + + waitForIdleSync(mFooter.mHandler); + verify(mFooterIcon).setVisibility(View.VISIBLE); + verify(mFooterIcon, never()).setImageResource(anyInt()); + verify(mFooterIcon2).setVisibility(View.VISIBLE); + verify(mFooterIcon2, never()).setImageResource(anyInt()); + } + + @Test public void testGetMessageWithNoOrganizationAndNoVPN() { assertEquals(getExpectedMessage(false /* hasDeviceOwnerOrganization */, false /* hasVPN */), mFooter.getMessage(DEVICE_OWNER_PACKAGE, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 45f369819ea8..6492a23c236a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -9856,9 +9856,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return false; } - Preconditions.checkNotNull(admin); synchronized (this) { - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + enforceDeviceOwnerOrManageUsers(); return isNetworkLoggingEnabledInternalLocked(); } } |