Merge "AudioDeviceBroker: Bluetooth LE communication route compatibility" am: 4d22fe5fac am: 3e71128e50
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2232053 Change-Id: I9d6f068534fe3740c2811b70705f0788f529795f Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -17,9 +17,12 @@ package com.android.server.audio;
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.app.compat.CompatChanges;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.bluetooth.BluetoothHeadset;
|
import android.bluetooth.BluetoothHeadset;
|
||||||
import android.bluetooth.BluetoothProfile;
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
import android.compat.annotation.ChangeId;
|
||||||
|
import android.compat.annotation.EnabledSince;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -118,8 +121,39 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
// TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
|
// TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
|
||||||
/*package*/ final Object mSetModeLock = new Object();
|
/*package*/ final Object mSetModeLock = new Object();
|
||||||
|
|
||||||
/** PID of current audio mode owner communicated by AudioService */
|
/** AudioModeInfo contains information on current audio mode owner
|
||||||
private int mModeOwnerPid = 0;
|
* communicated by AudioService */
|
||||||
|
/* package */ static final class AudioModeInfo {
|
||||||
|
/** Current audio mode */
|
||||||
|
final int mMode;
|
||||||
|
/** PID of current audio mode owner */
|
||||||
|
final int mPid;
|
||||||
|
/** UID of current audio mode owner */
|
||||||
|
final int mUid;
|
||||||
|
|
||||||
|
AudioModeInfo(int mode, int pid, int uid) {
|
||||||
|
mMode = mode;
|
||||||
|
mPid = pid;
|
||||||
|
mUid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AudioModeInfo: mMode=" + AudioSystem.modeToString(mMode)
|
||||||
|
+ ", mPid=" + mPid
|
||||||
|
+ ", mUid=" + mUid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private AudioModeInfo mAudioModeOwner = new AudioModeInfo(AudioSystem.MODE_NORMAL, 0, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that default communication device is chosen by routing rules in audio policy
|
||||||
|
* manager and not forced by AudioDeviceBroker.
|
||||||
|
*/
|
||||||
|
@ChangeId
|
||||||
|
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2)
|
||||||
|
public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L;
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
/*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
|
/*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
|
||||||
@@ -187,7 +221,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
/*package*/ void onSystemReady() {
|
/*package*/ void onSystemReady() {
|
||||||
synchronized (mSetModeLock) {
|
synchronized (mSetModeLock) {
|
||||||
synchronized (mDeviceStateLock) {
|
synchronized (mDeviceStateLock) {
|
||||||
mModeOwnerPid = mAudioService.getModeOwnerPid();
|
mAudioModeOwner = mAudioService.getAudioModeOwner();
|
||||||
mBtHelper.onSystemReady();
|
mBtHelper.onSystemReady();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,11 +402,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
@GuardedBy("mDeviceStateLock")
|
@GuardedBy("mDeviceStateLock")
|
||||||
private CommunicationRouteClient topCommunicationRouteClient() {
|
private CommunicationRouteClient topCommunicationRouteClient() {
|
||||||
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
|
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
|
||||||
if (crc.getPid() == mModeOwnerPid) {
|
if (crc.getPid() == mAudioModeOwner.mPid) {
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) {
|
if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) {
|
||||||
return mCommunicationRouteClients.get(0);
|
return mCommunicationRouteClients.get(0);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -390,7 +424,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
|
AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
|
||||||
if (AudioService.DEBUG_COMM_RTE) {
|
if (AudioService.DEBUG_COMM_RTE) {
|
||||||
Log.v(TAG, "requestedCommunicationDevice, device: "
|
Log.v(TAG, "requestedCommunicationDevice, device: "
|
||||||
+ device + " mode owner pid: " + mModeOwnerPid);
|
+ device + "mAudioModeOwner: " + mAudioModeOwner.toString());
|
||||||
}
|
}
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
@@ -774,8 +808,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
sendLMsgNoDelay(MSG_II_SET_LE_AUDIO_OUT_VOLUME, SENDMSG_REPLACE, info);
|
sendLMsgNoDelay(MSG_II_SET_LE_AUDIO_OUT_VOLUME, SENDMSG_REPLACE, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ void postSetModeOwnerPid(int pid, int mode) {
|
/*package*/ void postSetModeOwner(int mode, int pid, int uid) {
|
||||||
sendIIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid, mode);
|
sendLMsgNoDelay(MSG_I_SET_MODE_OWNER, SENDMSG_REPLACE,
|
||||||
|
new AudioModeInfo(mode, pid, uid));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
|
/*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
|
||||||
@@ -1162,7 +1197,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
pw.println(prefix + "mAccessibilityStrategyId: "
|
pw.println(prefix + "mAccessibilityStrategyId: "
|
||||||
+ mAccessibilityStrategyId);
|
+ mAccessibilityStrategyId);
|
||||||
|
|
||||||
pw.println("\n" + prefix + "mModeOwnerPid: " + mModeOwnerPid);
|
pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner);
|
||||||
|
|
||||||
mBtHelper.dump(pw, prefix);
|
mBtHelper.dump(pw, prefix);
|
||||||
}
|
}
|
||||||
@@ -1288,12 +1323,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MSG_L_SET_BT_ACTIVE_DEVICE:
|
case MSG_L_SET_BT_ACTIVE_DEVICE:
|
||||||
synchronized (mDeviceStateLock) {
|
synchronized (mSetModeLock) {
|
||||||
BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
|
synchronized (mDeviceStateLock) {
|
||||||
mDeviceInventory.onSetBtActiveDevice(btInfo,
|
BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
|
||||||
(btInfo.mProfile != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
|
mDeviceInventory.onSetBtActiveDevice(btInfo,
|
||||||
? mAudioService.getBluetoothContextualVolumeStream()
|
(btInfo.mProfile
|
||||||
: AudioSystem.STREAM_DEFAULT);
|
!= BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
|
||||||
|
? mAudioService.getBluetoothContextualVolumeStream()
|
||||||
|
: AudioSystem.STREAM_DEFAULT);
|
||||||
|
if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
|
||||||
|
|| btInfo.mProfile == BluetoothProfile.HEARING_AID) {
|
||||||
|
onUpdateCommunicationRouteClient("setBluetoothActiveDevice");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MSG_BT_HEADSET_CNCT_FAILED:
|
case MSG_BT_HEADSET_CNCT_FAILED:
|
||||||
@@ -1338,11 +1380,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
|
mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MSG_I_SET_MODE_OWNER_PID:
|
case MSG_I_SET_MODE_OWNER:
|
||||||
synchronized (mSetModeLock) {
|
synchronized (mSetModeLock) {
|
||||||
synchronized (mDeviceStateLock) {
|
synchronized (mDeviceStateLock) {
|
||||||
mModeOwnerPid = msg.arg1;
|
mAudioModeOwner = (AudioModeInfo) msg.obj;
|
||||||
if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
|
if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) {
|
||||||
onUpdateCommunicationRouteClient("setNewModeOwner");
|
onUpdateCommunicationRouteClient("setNewModeOwner");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1504,7 +1546,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private static final int MSG_REPORT_NEW_ROUTES = 13;
|
private static final int MSG_REPORT_NEW_ROUTES = 13;
|
||||||
private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
|
private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
|
||||||
private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
|
private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
|
||||||
private static final int MSG_I_SET_MODE_OWNER_PID = 16;
|
private static final int MSG_I_SET_MODE_OWNER = 16;
|
||||||
|
|
||||||
private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22;
|
private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22;
|
||||||
private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23;
|
private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23;
|
||||||
@@ -1826,8 +1868,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
AudioSystem.setParameters("BT_SCO=on");
|
AudioSystem.setParameters("BT_SCO=on");
|
||||||
}
|
}
|
||||||
if (preferredCommunicationDevice == null) {
|
if (preferredCommunicationDevice == null) {
|
||||||
removePreferredDevicesForStrategySync(mCommunicationStrategyId);
|
AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
|
||||||
removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
|
if (defaultDevice != null) {
|
||||||
|
setPreferredDevicesForStrategySync(
|
||||||
|
mCommunicationStrategyId, Arrays.asList(defaultDevice));
|
||||||
|
setPreferredDevicesForStrategySync(
|
||||||
|
mAccessibilityStrategyId, Arrays.asList(defaultDevice));
|
||||||
|
} else {
|
||||||
|
removePreferredDevicesForStrategySync(mCommunicationStrategyId);
|
||||||
|
removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setPreferredDevicesForStrategySync(
|
setPreferredDevicesForStrategySync(
|
||||||
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
|
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
|
||||||
@@ -1856,26 +1906,24 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @GuardedBy("mSetModeLock")
|
||||||
|
@GuardedBy("mDeviceStateLock")
|
||||||
private void onUpdatePhoneStrategyDevice(AudioDeviceAttributes device) {
|
private void onUpdatePhoneStrategyDevice(AudioDeviceAttributes device) {
|
||||||
synchronized (mSetModeLock) {
|
boolean wasSpeakerphoneActive = isSpeakerphoneActive();
|
||||||
synchronized (mDeviceStateLock) {
|
mPreferredCommunicationDevice = device;
|
||||||
boolean wasSpeakerphoneActive = isSpeakerphoneActive();
|
updateActiveCommunicationDevice();
|
||||||
mPreferredCommunicationDevice = device;
|
if (wasSpeakerphoneActive != isSpeakerphoneActive()) {
|
||||||
updateActiveCommunicationDevice();
|
try {
|
||||||
if (wasSpeakerphoneActive != isSpeakerphoneActive()) {
|
mContext.sendBroadcastAsUser(
|
||||||
try {
|
new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
|
||||||
mContext.sendBroadcastAsUser(
|
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
|
||||||
new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
|
UserHandle.ALL);
|
||||||
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
|
} catch (Exception e) {
|
||||||
UserHandle.ALL);
|
Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e);
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mAudioService.postUpdateRingerModeServiceInt();
|
|
||||||
dispatchCommunicationDevice();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mAudioService.postUpdateRingerModeServiceInt();
|
||||||
|
dispatchCommunicationDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommunicationRouteClient removeCommunicationRouteClient(
|
private CommunicationRouteClient removeCommunicationRouteClient(
|
||||||
@@ -1915,6 +1963,32 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GuardedBy("mDeviceStateLock")
|
||||||
|
private boolean communnicationDeviceCompatOn() {
|
||||||
|
return mAudioModeOwner.mMode == AudioSystem.MODE_IN_COMMUNICATION
|
||||||
|
&& !(CompatChanges.isChangeEnabled(
|
||||||
|
USE_SET_COMMUNICATION_DEVICE, mAudioModeOwner.mUid)
|
||||||
|
|| mAudioModeOwner.mUid == android.os.Process.SYSTEM_UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("mDeviceStateLock")
|
||||||
|
AudioDeviceAttributes getDefaultCommunicationDevice() {
|
||||||
|
// For system server (Telecom) and APKs targeting S and above, we let the audio
|
||||||
|
// policy routing rules select the default communication device.
|
||||||
|
// For older APKs, we force Hearing Aid or LE Audio headset when connected as
|
||||||
|
// those APKs cannot select a LE Audio or Hearing Aid device explicitly.
|
||||||
|
AudioDeviceAttributes device = null;
|
||||||
|
if (communnicationDeviceCompatOn()) {
|
||||||
|
// If both LE and Hearing Aid are active (thie should not happen),
|
||||||
|
// priority to Hearing Aid.
|
||||||
|
device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_HEARING_AID);
|
||||||
|
if (device == null) {
|
||||||
|
device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_BLE_HEADSET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
|
@Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
|
||||||
synchronized (mDeviceStateLock) {
|
synchronized (mDeviceStateLock) {
|
||||||
return mDeviceInventory.getDeviceSensorUuid(device);
|
return mDeviceInventory.getDeviceSensorUuid(device);
|
||||||
|
|||||||
@@ -294,6 +294,7 @@ public class AudioDeviceInventory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
|
||||||
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
|
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
|
||||||
void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) {
|
void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) {
|
||||||
if (AudioService.DEBUG_DEVICES) {
|
if (AudioService.DEBUG_DEVICES) {
|
||||||
@@ -1526,6 +1527,19 @@ public class AudioDeviceInventory {
|
|||||||
return di.mSensorUuid;
|
return di.mSensorUuid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* package */ AudioDeviceAttributes getDeviceOfType(int type) {
|
||||||
|
synchronized (mDevicesLock) {
|
||||||
|
for (DeviceInfo di : mConnectedDevices.values()) {
|
||||||
|
if (di.mDeviceType == type) {
|
||||||
|
return new AudioDeviceAttributes(
|
||||||
|
di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------
|
//----------------------------------------------------------
|
||||||
// For tests only
|
// For tests only
|
||||||
|
|
||||||
|
|||||||
@@ -5049,16 +5049,17 @@ public class AudioService extends IAudioService.Stub
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the pid of the current audio mode owner
|
* Return information on the current audio mode owner
|
||||||
* @return 0 if nobody owns the mode
|
* @return 0 if nobody owns the mode
|
||||||
*/
|
*/
|
||||||
@GuardedBy("mDeviceBroker.mSetModeLock")
|
@GuardedBy("mDeviceBroker.mSetModeLock")
|
||||||
/*package*/ int getModeOwnerPid() {
|
/*package*/ AudioDeviceBroker.AudioModeInfo getAudioModeOwner() {
|
||||||
SetModeDeathHandler hdlr = getAudioModeOwnerHandler();
|
SetModeDeathHandler hdlr = getAudioModeOwnerHandler();
|
||||||
if (hdlr != null) {
|
if (hdlr != null) {
|
||||||
return hdlr.getPid();
|
return new AudioDeviceBroker.AudioModeInfo(
|
||||||
|
hdlr.getMode(), hdlr.getPid(), hdlr.getUid());
|
||||||
}
|
}
|
||||||
return 0;
|
return new AudioDeviceBroker.AudioModeInfo(AudioSystem.MODE_NORMAL, 0 , 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5244,7 +5245,7 @@ public class AudioService extends IAudioService.Stub
|
|||||||
|
|
||||||
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
|
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
|
||||||
// connections not started by the application changing the mode when pid changes
|
// connections not started by the application changing the mode when pid changes
|
||||||
mDeviceBroker.postSetModeOwnerPid(pid, mode);
|
mDeviceBroker.postSetModeOwner(mode, pid, uid);
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
|
Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
|
||||||
}
|
}
|
||||||
@@ -5571,7 +5572,10 @@ public class AudioService extends IAudioService.Stub
|
|||||||
}
|
}
|
||||||
return deviceIds.stream().mapToInt(Integer::intValue).toArray();
|
return deviceIds.stream().mapToInt(Integer::intValue).toArray();
|
||||||
}
|
}
|
||||||
/** @see AudioManager#setCommunicationDevice(int) */
|
/**
|
||||||
|
* @see AudioManager#setCommunicationDevice(int)
|
||||||
|
* @see AudioManager#clearCommunicationDevice()
|
||||||
|
*/
|
||||||
public boolean setCommunicationDevice(IBinder cb, int portId) {
|
public boolean setCommunicationDevice(IBinder cb, int portId) {
|
||||||
final int uid = Binder.getCallingUid();
|
final int uid = Binder.getCallingUid();
|
||||||
final int pid = Binder.getCallingPid();
|
final int pid = Binder.getCallingPid();
|
||||||
@@ -5586,7 +5590,8 @@ public class AudioService extends IAudioService.Stub
|
|||||||
throw new IllegalArgumentException("invalid device type " + device.getType());
|
throw new IllegalArgumentException("invalid device type " + device.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final String eventSource = new StringBuilder("setCommunicationDevice(")
|
final String eventSource = new StringBuilder()
|
||||||
|
.append(device == null ? "clearCommunicationDevice(" : "setCommunicationDevice(")
|
||||||
.append(") from u/pid:").append(uid).append("/")
|
.append(") from u/pid:").append(uid).append("/")
|
||||||
.append(pid).toString();
|
.append(pid).toString();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user