Loading packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +258 −16 Original line number Diff line number Diff line Loading @@ -29,18 +29,23 @@ import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.text.TextUtils; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.settingslib.R; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; /** * LocalBluetoothLeBroadcast provides an interface between the Settings app Loading @@ -55,13 +60,26 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { static final String NAME = "LE_AUDIO_BROADCAST"; // Order of this profile in device profiles list private static final int ORDINAL = 1; private static final String PREF_NAME = "LocalBluetoothLeBroadcast"; private static final String PREF_PROGRAM_INFO = "PrefProgramInfo"; private static final String PREF_BROADCAST_CODE = "PrefBroadcastCode"; private static final String PREF_APP_SOURCE_NAME = "PrefAppSourceName"; private static final String UNDERLINE = "_"; private static final int DEFAULT_CODE_MIN = 1000; private static final int DEFAULT_CODE_MAX = 9999; private BluetoothLeBroadcast mService; private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata; private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; private BluetoothLeAudioContentMetadata.Builder mBuilder; private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; private String mAppSourceName = ""; private String mNewAppSourceName = ""; private boolean mIsProfileReady; private String mProgramInfo; private byte[] mBroadcastCode; private SharedPreferences mSharedPref; private Executor mExecutor; private final ServiceListener mServiceListener = new ServiceListener() { @Override Loading @@ -71,41 +89,232 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } mService = (BluetoothLeBroadcast) proxy; mIsProfileReady = true; registerServiceCallBack(mExecutor, mBroadcastCallback); } @Override public void onServiceDisconnected(int profile) { if (profile != BluetoothProfile.LE_AUDIO_BROADCAST) { Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST"); return; } if (DEBUG) { Log.d(TAG, "Bluetooth service disconnected"); } mIsProfileReady = false; unregisterServiceCallBack(mBroadcastCallback); } }; private final BluetoothLeBroadcast.Callback mBroadcastCallback = new BluetoothLeBroadcast.Callback() { @Override public void onBroadcastStarted(int reason, int broadcastId) { if (DEBUG) { Log.d(TAG, "onBroadcastStarted(), reason = " + reason + ", broadcastId = " + broadcastId); } setLatestBroadcastId(broadcastId); setAppSourceName(mNewAppSourceName); } @Override public void onBroadcastStartFailed(int reason) { if (DEBUG) { Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason); } } @Override public void onBroadcastMetadataChanged(int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) { if (DEBUG) { Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId); } setLatestBluetoothLeBroadcastMetadata(metadata); } @Override public void onBroadcastStopped(int reason, int broadcastId) { if (DEBUG) { Log.d(TAG, "onBroadcastStopped(), reason = " + reason + ", broadcastId = " + broadcastId); } resetCacheInfo(); } @Override public void onBroadcastStopFailed(int reason) { if (DEBUG) { Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason); } } @Override public void onBroadcastUpdated(int reason, int broadcastId) { if (DEBUG) { Log.d(TAG, "onBroadcastUpdated(), reason = " + reason + ", broadcastId = " + broadcastId); } setLatestBroadcastId(broadcastId); setAppSourceName(mNewAppSourceName); } @Override public void onBroadcastUpdateFailed(int reason, int broadcastId) { if (DEBUG) { Log.d(TAG, "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = " + broadcastId); } } @Override public void onPlaybackStarted(int reason, int broadcastId) { } @Override public void onPlaybackStopped(int reason, int broadcastId) { } }; LocalBluetoothLeBroadcast(Context context) { mExecutor = Executors.newSingleThreadExecutor(); BluetoothAdapter.getDefaultAdapter(). getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); mBuilder = new BluetoothLeAudioContentMetadata.Builder(); mSharedPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); if (mSharedPref != null) { String programInfo = mSharedPref.getString(PREF_PROGRAM_INFO, ""); if (programInfo.isEmpty()) { programInfo = getDefaultValueOfProgramInfo(); } setProgramInfo(programInfo); public void startBroadcast(byte[] broadcastCode, String language, String programInfo) { String prefBroadcastCode = mSharedPref.getString(PREF_BROADCAST_CODE, ""); byte[] broadcastCode; if (prefBroadcastCode.isEmpty()) { broadcastCode = getDefaultValueOfBroadcastCode(); } else { broadcastCode = prefBroadcastCode.getBytes(StandardCharsets.UTF_8); } setBroadcastCode(broadcastCode); mAppSourceName = mSharedPref.getString(PREF_APP_SOURCE_NAME, ""); } } /** * Start the LE Broadcast. If the system started the LE Broadcast, then the system calls the * corresponding callback {@link BluetoothLeBroadcast.Callback}. */ public void startBroadcast(String appSourceName, String language) { mNewAppSourceName = appSourceName; if (mService == null) { Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast."); return; } if (DEBUG) { Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo); Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + mProgramInfo); } buildContentMetadata(language, mProgramInfo); mService.startBroadcast(mBluetoothLeAudioContentMetadata, mBroadcastCode); } public String getProgramInfo() { return mProgramInfo; } public void setProgramInfo(String programInfo) { if (programInfo == null || programInfo.isEmpty()) { Log.d(TAG, "setProgramInfo: programInfo is null or empty"); return; } Log.d(TAG, "setProgramInfo: " + programInfo); mProgramInfo = programInfo; if (mSharedPref == null) { Log.d(TAG, "setProgramInfo: sharedPref is null"); return; } SharedPreferences.Editor editor = mSharedPref.edit(); editor.putString(PREF_PROGRAM_INFO, mProgramInfo); editor.apply(); } public byte[] getBroadcastCode() { return mBroadcastCode; } public void setBroadcastCode(byte[] broadcastCode) { if (broadcastCode == null || broadcastCode.length == 0) { Log.d(TAG, "setBroadcastCode: broadcastCode is null or empty"); return; } mBroadcastCode = broadcastCode; if (mSharedPref == null) { Log.d(TAG, "setBroadcastCode: sharedPref is null"); return; } buildContentMetadata(language, programInfo); mService.startBroadcast(mBluetoothLeAudioContentMetadata, broadcastCode); SharedPreferences.Editor editor = mSharedPref.edit(); editor.putString(PREF_BROADCAST_CODE, new String(broadcastCode, StandardCharsets.UTF_8)); editor.apply(); } public void stopBroadcast() { private void setLatestBroadcastId(int broadcastId) { mBroadcastId = broadcastId; } public int getLatestBroadcastId() { return mBroadcastId; } private void setAppSourceName(String appSourceName) { if (TextUtils.isEmpty(appSourceName)) { appSourceName = ""; } mAppSourceName = appSourceName; mNewAppSourceName = ""; if (mSharedPref == null) { Log.d(TAG, "setBroadcastCode: sharedPref is null"); return; } SharedPreferences.Editor editor = mSharedPref.edit(); editor.putString(PREF_APP_SOURCE_NAME, appSourceName); editor.apply(); } public String getAppSourceName() { return mAppSourceName; } private void setLatestBluetoothLeBroadcastMetadata( BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata) { if (bluetoothLeBroadcastMetadata != null && bluetoothLeBroadcastMetadata.getBroadcastId() == mBroadcastId) { mBluetoothLeBroadcastMetadata = bluetoothLeBroadcastMetadata; } } public BluetoothLeBroadcastMetadata getLatestBluetoothLeBroadcastMetadata() { return mBluetoothLeBroadcastMetadata; } /** * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system * calls the corresponding callback {@link BluetoothLeBroadcast.Callback}. */ public void stopLatestBroadcast() { stopBroadcast(mBroadcastId); } /** * Stop the LE Broadcast. If the system stopped the LE Broadcast, then the system calls the * corresponding callback {@link BluetoothLeBroadcast.Callback}. */ public void stopBroadcast(int broadcastId) { if (mService == null) { Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast."); return; Loading @@ -113,19 +322,24 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { if (DEBUG) { Log.d(TAG, "stopBroadcast()"); } mService.stopBroadcast(mBroadcastId); mService.stopBroadcast(broadcastId); } public void updateBroadcast(String language, String programInfo) { /** * Update the LE Broadcast. If the system stopped the LE Broadcast, then the system calls the * corresponding callback {@link BluetoothLeBroadcast.Callback}. */ public void updateBroadcast(String appSourceName, String language) { if (mService == null) { Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast."); return; } if (DEBUG) { Log.d(TAG, "updateBroadcast: language = " + language + " ,programInfo = " + programInfo); "updateBroadcast: language = " + language + " ,programInfo = " + mProgramInfo); } mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build(); mNewAppSourceName = appSourceName; mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(mProgramInfo).build(); mService.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata); } Loading Loading @@ -196,7 +410,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { return mService.getConnectedDevices(); } public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() { public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() { if (mService == null) { Log.d(TAG, "The BluetoothLeBroadcast is null."); return Collections.emptyList(); Loading Loading @@ -265,4 +480,31 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } } } private String getDefaultValueOfProgramInfo() { //set the default value; int postfix = ThreadLocalRandom.current().nextInt(DEFAULT_CODE_MIN, DEFAULT_CODE_MAX); return BluetoothAdapter.getDefaultAdapter().getName() + UNDERLINE + postfix; } private byte[] getDefaultValueOfBroadcastCode() { //set the default value; return generateRandomPassword().getBytes(StandardCharsets.UTF_8); } private void resetCacheInfo() { if (DEBUG) { Log.d(TAG, "resetCacheInfo:"); } mNewAppSourceName = ""; mAppSourceName = ""; mBluetoothLeBroadcastMetadata = null; mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; } private String generateRandomPassword() { String randomUUID = UUID.randomUUID().toString(); //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx return randomUUID.substring(0, 8) + randomUUID.substring(9, 13); } } Loading
packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +258 −16 Original line number Diff line number Diff line Loading @@ -29,18 +29,23 @@ import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.text.TextUtils; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.settingslib.R; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; /** * LocalBluetoothLeBroadcast provides an interface between the Settings app Loading @@ -55,13 +60,26 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { static final String NAME = "LE_AUDIO_BROADCAST"; // Order of this profile in device profiles list private static final int ORDINAL = 1; private static final String PREF_NAME = "LocalBluetoothLeBroadcast"; private static final String PREF_PROGRAM_INFO = "PrefProgramInfo"; private static final String PREF_BROADCAST_CODE = "PrefBroadcastCode"; private static final String PREF_APP_SOURCE_NAME = "PrefAppSourceName"; private static final String UNDERLINE = "_"; private static final int DEFAULT_CODE_MIN = 1000; private static final int DEFAULT_CODE_MAX = 9999; private BluetoothLeBroadcast mService; private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata; private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; private BluetoothLeAudioContentMetadata.Builder mBuilder; private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; private String mAppSourceName = ""; private String mNewAppSourceName = ""; private boolean mIsProfileReady; private String mProgramInfo; private byte[] mBroadcastCode; private SharedPreferences mSharedPref; private Executor mExecutor; private final ServiceListener mServiceListener = new ServiceListener() { @Override Loading @@ -71,41 +89,232 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } mService = (BluetoothLeBroadcast) proxy; mIsProfileReady = true; registerServiceCallBack(mExecutor, mBroadcastCallback); } @Override public void onServiceDisconnected(int profile) { if (profile != BluetoothProfile.LE_AUDIO_BROADCAST) { Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST"); return; } if (DEBUG) { Log.d(TAG, "Bluetooth service disconnected"); } mIsProfileReady = false; unregisterServiceCallBack(mBroadcastCallback); } }; private final BluetoothLeBroadcast.Callback mBroadcastCallback = new BluetoothLeBroadcast.Callback() { @Override public void onBroadcastStarted(int reason, int broadcastId) { if (DEBUG) { Log.d(TAG, "onBroadcastStarted(), reason = " + reason + ", broadcastId = " + broadcastId); } setLatestBroadcastId(broadcastId); setAppSourceName(mNewAppSourceName); } @Override public void onBroadcastStartFailed(int reason) { if (DEBUG) { Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason); } } @Override public void onBroadcastMetadataChanged(int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) { if (DEBUG) { Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId); } setLatestBluetoothLeBroadcastMetadata(metadata); } @Override public void onBroadcastStopped(int reason, int broadcastId) { if (DEBUG) { Log.d(TAG, "onBroadcastStopped(), reason = " + reason + ", broadcastId = " + broadcastId); } resetCacheInfo(); } @Override public void onBroadcastStopFailed(int reason) { if (DEBUG) { Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason); } } @Override public void onBroadcastUpdated(int reason, int broadcastId) { if (DEBUG) { Log.d(TAG, "onBroadcastUpdated(), reason = " + reason + ", broadcastId = " + broadcastId); } setLatestBroadcastId(broadcastId); setAppSourceName(mNewAppSourceName); } @Override public void onBroadcastUpdateFailed(int reason, int broadcastId) { if (DEBUG) { Log.d(TAG, "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = " + broadcastId); } } @Override public void onPlaybackStarted(int reason, int broadcastId) { } @Override public void onPlaybackStopped(int reason, int broadcastId) { } }; LocalBluetoothLeBroadcast(Context context) { mExecutor = Executors.newSingleThreadExecutor(); BluetoothAdapter.getDefaultAdapter(). getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); mBuilder = new BluetoothLeAudioContentMetadata.Builder(); mSharedPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); if (mSharedPref != null) { String programInfo = mSharedPref.getString(PREF_PROGRAM_INFO, ""); if (programInfo.isEmpty()) { programInfo = getDefaultValueOfProgramInfo(); } setProgramInfo(programInfo); public void startBroadcast(byte[] broadcastCode, String language, String programInfo) { String prefBroadcastCode = mSharedPref.getString(PREF_BROADCAST_CODE, ""); byte[] broadcastCode; if (prefBroadcastCode.isEmpty()) { broadcastCode = getDefaultValueOfBroadcastCode(); } else { broadcastCode = prefBroadcastCode.getBytes(StandardCharsets.UTF_8); } setBroadcastCode(broadcastCode); mAppSourceName = mSharedPref.getString(PREF_APP_SOURCE_NAME, ""); } } /** * Start the LE Broadcast. If the system started the LE Broadcast, then the system calls the * corresponding callback {@link BluetoothLeBroadcast.Callback}. */ public void startBroadcast(String appSourceName, String language) { mNewAppSourceName = appSourceName; if (mService == null) { Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast."); return; } if (DEBUG) { Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo); Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + mProgramInfo); } buildContentMetadata(language, mProgramInfo); mService.startBroadcast(mBluetoothLeAudioContentMetadata, mBroadcastCode); } public String getProgramInfo() { return mProgramInfo; } public void setProgramInfo(String programInfo) { if (programInfo == null || programInfo.isEmpty()) { Log.d(TAG, "setProgramInfo: programInfo is null or empty"); return; } Log.d(TAG, "setProgramInfo: " + programInfo); mProgramInfo = programInfo; if (mSharedPref == null) { Log.d(TAG, "setProgramInfo: sharedPref is null"); return; } SharedPreferences.Editor editor = mSharedPref.edit(); editor.putString(PREF_PROGRAM_INFO, mProgramInfo); editor.apply(); } public byte[] getBroadcastCode() { return mBroadcastCode; } public void setBroadcastCode(byte[] broadcastCode) { if (broadcastCode == null || broadcastCode.length == 0) { Log.d(TAG, "setBroadcastCode: broadcastCode is null or empty"); return; } mBroadcastCode = broadcastCode; if (mSharedPref == null) { Log.d(TAG, "setBroadcastCode: sharedPref is null"); return; } buildContentMetadata(language, programInfo); mService.startBroadcast(mBluetoothLeAudioContentMetadata, broadcastCode); SharedPreferences.Editor editor = mSharedPref.edit(); editor.putString(PREF_BROADCAST_CODE, new String(broadcastCode, StandardCharsets.UTF_8)); editor.apply(); } public void stopBroadcast() { private void setLatestBroadcastId(int broadcastId) { mBroadcastId = broadcastId; } public int getLatestBroadcastId() { return mBroadcastId; } private void setAppSourceName(String appSourceName) { if (TextUtils.isEmpty(appSourceName)) { appSourceName = ""; } mAppSourceName = appSourceName; mNewAppSourceName = ""; if (mSharedPref == null) { Log.d(TAG, "setBroadcastCode: sharedPref is null"); return; } SharedPreferences.Editor editor = mSharedPref.edit(); editor.putString(PREF_APP_SOURCE_NAME, appSourceName); editor.apply(); } public String getAppSourceName() { return mAppSourceName; } private void setLatestBluetoothLeBroadcastMetadata( BluetoothLeBroadcastMetadata bluetoothLeBroadcastMetadata) { if (bluetoothLeBroadcastMetadata != null && bluetoothLeBroadcastMetadata.getBroadcastId() == mBroadcastId) { mBluetoothLeBroadcastMetadata = bluetoothLeBroadcastMetadata; } } public BluetoothLeBroadcastMetadata getLatestBluetoothLeBroadcastMetadata() { return mBluetoothLeBroadcastMetadata; } /** * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system * calls the corresponding callback {@link BluetoothLeBroadcast.Callback}. */ public void stopLatestBroadcast() { stopBroadcast(mBroadcastId); } /** * Stop the LE Broadcast. If the system stopped the LE Broadcast, then the system calls the * corresponding callback {@link BluetoothLeBroadcast.Callback}. */ public void stopBroadcast(int broadcastId) { if (mService == null) { Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast."); return; Loading @@ -113,19 +322,24 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { if (DEBUG) { Log.d(TAG, "stopBroadcast()"); } mService.stopBroadcast(mBroadcastId); mService.stopBroadcast(broadcastId); } public void updateBroadcast(String language, String programInfo) { /** * Update the LE Broadcast. If the system stopped the LE Broadcast, then the system calls the * corresponding callback {@link BluetoothLeBroadcast.Callback}. */ public void updateBroadcast(String appSourceName, String language) { if (mService == null) { Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast."); return; } if (DEBUG) { Log.d(TAG, "updateBroadcast: language = " + language + " ,programInfo = " + programInfo); "updateBroadcast: language = " + language + " ,programInfo = " + mProgramInfo); } mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build(); mNewAppSourceName = appSourceName; mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(mProgramInfo).build(); mService.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata); } Loading Loading @@ -196,7 +410,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { return mService.getConnectedDevices(); } public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() { public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() { if (mService == null) { Log.d(TAG, "The BluetoothLeBroadcast is null."); return Collections.emptyList(); Loading Loading @@ -265,4 +480,31 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } } } private String getDefaultValueOfProgramInfo() { //set the default value; int postfix = ThreadLocalRandom.current().nextInt(DEFAULT_CODE_MIN, DEFAULT_CODE_MAX); return BluetoothAdapter.getDefaultAdapter().getName() + UNDERLINE + postfix; } private byte[] getDefaultValueOfBroadcastCode() { //set the default value; return generateRandomPassword().getBytes(StandardCharsets.UTF_8); } private void resetCacheInfo() { if (DEBUG) { Log.d(TAG, "resetCacheInfo:"); } mNewAppSourceName = ""; mAppSourceName = ""; mBluetoothLeBroadcastMetadata = null; mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; } private String generateRandomPassword() { String randomUUID = UUID.randomUUID().toString(); //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx return randomUUID.substring(0, 8) + randomUUID.substring(9, 13); } }