diff --git a/api/current.txt b/api/current.txt index 026392736e2a4..0774a9ac21036 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5776,6 +5776,7 @@ package android.app.admin { method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy(); method public java.util.List getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName); method public android.os.Bundle getUserRestrictions(android.content.ComponentName); + method public java.lang.String getWifiMacAddress(); method public boolean hasCaCertInstalled(android.content.ComponentName, byte[]); method public boolean hasGrantedPolicy(android.content.ComponentName, int); method public boolean installCaCert(android.content.ComponentName, byte[]); diff --git a/api/system-current.txt b/api/system-current.txt index 621a2820b3acf..7c2e4be9089c6 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5906,6 +5906,7 @@ package android.app.admin { method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy(); method public java.util.List getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName); method public android.os.Bundle getUserRestrictions(android.content.ComponentName); + method public java.lang.String getWifiMacAddress(); method public boolean hasCaCertInstalled(android.content.ComponentName, byte[]); method public boolean hasGrantedPolicy(android.content.ComponentName, int); method public boolean installCaCert(android.content.ComponentName, byte[]); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 9aac1705012fd..471750eda0cf8 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4649,4 +4649,21 @@ public class DevicePolicyManager { return false; } } + + /** + * Called by device owner to get the MAC address of the Wi-Fi device. + * + * @return the MAC address of the Wi-Fi device, or null when the information is not + * available. (For example, Wi-Fi hasn't been enabled, or the device doesn't support Wi-Fi.) + * + *

The address will be in the {@code XX:XX:XX:XX:XX:XX} format. + */ + public String getWifiMacAddress() { + try { + return mService.getWifiMacAddress(); + } catch (RemoteException re) { + Log.w(TAG, "Failed talking with device policy service", re); + return null; + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index e1986267e61b3..6b4567c297c53 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -236,4 +236,5 @@ interface IDevicePolicyManager { List getKeepUninstalledPackages(in ComponentName admin); boolean isManagedProfile(in ComponentName admin); boolean isSystemOnlyUser(in ComponentName admin); + String getWifiMacAddress(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index b3b647fa7fff9..5c3a55dde579f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -71,6 +71,8 @@ import android.media.IAudioService; import android.net.ConnectivityManager; import android.net.ProxyInfo; import android.net.Uri; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; @@ -1121,6 +1123,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return Looper.myLooper(); } + WifiManager getWifiManager() { + return mContext.getSystemService(WifiManager.class); + } + long binderClearCallingIdentity() { return Binder.clearCallingIdentity(); } @@ -6871,6 +6877,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return true; } + @Override + public String getWifiMacAddress() { + // Make sure caller has DO. + synchronized (this) { + getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + + final long ident = mInjector.binderClearCallingIdentity(); + try { + final WifiInfo wifiInfo = mInjector.getWifiManager().getConnectionInfo(); + if (wifiInfo == null) { + return null; + } + return wifiInfo.hasRealMacAddress() ? wifiInfo.getMacAddress() : null; + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + /** * Returns the target sdk version number that the given packageName was built for * in the given user. diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 79ba08d50ec4c..01593568b699f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -26,10 +26,12 @@ import android.app.admin.DevicePolicyManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.pm.PackageManager; +import android.net.wifi.WifiInfo; import android.os.Bundle; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import android.test.MoreAsserts; import android.util.Pair; import org.mockito.ArgumentCaptor; @@ -1175,4 +1177,64 @@ public class DevicePolicyManagerTest extends DpmTestBase { // TODO Make sure restrictions are written to the file. } + + public void testGetMacAddress() throws Exception { + mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); + mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL); + + // In this test, change the caller user to "system". + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + + // Make sure admin1 is installed on system user. + setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID); + + // Test 1. Caller doesn't have DO or DA. + try { + dpm.getWifiMacAddress(); + fail(); + } catch (SecurityException e) { + MoreAsserts.assertContainsRegex("No active admin owned", e.getMessage()); + } + + // DO needs to be an DA. + dpm.setActiveAdmin(admin1, /* replace =*/ false); + assertTrue(dpm.isAdminActive(admin1)); + + // Test 2. Caller has DA, but not DO. + try { + dpm.getWifiMacAddress(); + fail(); + } catch (SecurityException e) { + MoreAsserts.assertContainsRegex("No active admin owned", e.getMessage()); + } + + // Test 3. Caller has PO, but not DO. + assertTrue(dpm.setProfileOwner(admin1, null, UserHandle.USER_SYSTEM)); + try { + dpm.getWifiMacAddress(); + fail(); + } catch (SecurityException e) { + MoreAsserts.assertContainsRegex("No active admin owned", e.getMessage()); + } + + // Remove PO. + dpm.clearProfileOwner(admin1); + + // Test 4, Caller is DO now. + assertTrue(dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM)); + + // 4-1. But no WifiInfo. + assertNull(dpm.getWifiMacAddress()); + + // 4-2. Returns WifiInfo, but with the default MAC. + when(mContext.wifiManager.getConnectionInfo()).thenReturn(new WifiInfo()); + assertNull(dpm.getWifiMacAddress()); + + // 4-3. With a real MAC address. + final WifiInfo wi = new WifiInfo(); + wi.setMacAddress("11:22:33:44:55:66"); + when(mContext.wifiManager.getConnectionInfo()).thenReturn(wi); + assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress()); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index bb1e06ddb35a9..66d701d0a3ca4 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -31,6 +31,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.media.IAudioService; +import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager.WakeLock; @@ -217,6 +218,7 @@ public class DpmMockContext extends MockContext { public final IBackupManager ibackupManager; public final IAudioService iaudioService; public final LockPatternUtils lockPatternUtils; + public final WifiManager wifiManager; public final SettingsForMock settings; public final MockContentResolver contentResolver; @@ -249,6 +251,7 @@ public class DpmMockContext extends MockContext { ibackupManager = mock(IBackupManager.class); iaudioService = mock(IAudioService.class); lockPatternUtils = mock(LockPatternUtils.class); + wifiManager = mock(WifiManager.class); settings = mock(SettingsForMock.class); // Package manager is huge, so we use a partial mock instead. @@ -303,6 +306,8 @@ public class DpmMockContext extends MockContext { return userManager; case Context.POWER_SERVICE: return powerManager; + case Context.WIFI_SERVICE: + return wifiManager; } throw new UnsupportedOperationException(); } diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index e25b38cc8175e..9f8af6e2224c1 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -424,6 +424,15 @@ public class WifiInfo implements Parcelable { return mMacAddress; } + /** + * @return true if {@link #getMacAddress()} has a real MAC address. + * + * @hide + */ + public boolean hasRealMacAddress() { + return mMacAddress != null && !DEFAULT_MAC_ADDRESS.equals(mMacAddress); + } + /** {@hide} */ public void setMeteredHint(boolean meteredHint) { mMeteredHint = meteredHint;