1 /** 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.broadcastradio.hal2; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.hardware.broadcastradio.V2_0.AmFmBandRange; 22 import android.hardware.broadcastradio.V2_0.AmFmRegionConfig; 23 import android.hardware.broadcastradio.V2_0.Announcement; 24 import android.hardware.broadcastradio.V2_0.DabTableEntry; 25 import android.hardware.broadcastradio.V2_0.IdentifierType; 26 import android.hardware.broadcastradio.V2_0.Metadata; 27 import android.hardware.broadcastradio.V2_0.MetadataKey; 28 import android.hardware.broadcastradio.V2_0.ProgramFilter; 29 import android.hardware.broadcastradio.V2_0.ProgramIdentifier; 30 import android.hardware.broadcastradio.V2_0.ProgramInfo; 31 import android.hardware.broadcastradio.V2_0.Properties; 32 import android.hardware.broadcastradio.V2_0.Result; 33 import android.hardware.broadcastradio.V2_0.VendorKeyValue; 34 import android.hardware.radio.ProgramList; 35 import android.hardware.radio.ProgramSelector; 36 import android.hardware.radio.RadioManager; 37 import android.hardware.radio.RadioMetadata; 38 import android.hardware.radio.RadioTuner; 39 import android.os.ParcelableException; 40 import android.util.ArrayMap; 41 import android.util.ArraySet; 42 import android.util.SparseArray; 43 44 import com.android.server.utils.Slogf; 45 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.Collection; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Objects; 52 import java.util.Set; 53 import java.util.stream.Collectors; 54 55 final class Convert { 56 57 private static final String TAG = "BcRadio2Srv.convert"; 58 Convert()59 private Convert() { 60 throw new UnsupportedOperationException("Convert class is noninstantiable"); 61 } 62 throwOnError(String action, int result)63 static void throwOnError(String action, int result) { 64 String errorString = action + ": " + Result.toString(result); 65 switch (result) { 66 case Result.OK: 67 return; 68 case Result.UNKNOWN_ERROR: 69 case Result.INTERNAL_ERROR: 70 case Result.TIMEOUT: 71 throw new ParcelableException(new RuntimeException(errorString)); 72 case Result.INVALID_ARGUMENTS: 73 throw new IllegalArgumentException(errorString); 74 case Result.INVALID_STATE: 75 throw new IllegalStateException(errorString); 76 case Result.NOT_SUPPORTED: 77 throw new UnsupportedOperationException(errorString); 78 default: 79 throw new ParcelableException(new RuntimeException( 80 action + ": unknown error (" + result + ")")); 81 } 82 } 83 84 @RadioTuner.TunerResultType halResultToTunerResult(int result)85 static int halResultToTunerResult(int result) { 86 switch (result) { 87 case Result.OK: 88 return RadioTuner.TUNER_RESULT_OK; 89 case Result.INTERNAL_ERROR: 90 return RadioTuner.TUNER_RESULT_INTERNAL_ERROR; 91 case Result.INVALID_ARGUMENTS: 92 return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS; 93 case Result.INVALID_STATE: 94 return RadioTuner.TUNER_RESULT_INVALID_STATE; 95 case Result.NOT_SUPPORTED: 96 return RadioTuner.TUNER_RESULT_NOT_SUPPORTED; 97 case Result.TIMEOUT: 98 return RadioTuner.TUNER_RESULT_TIMEOUT; 99 case Result.UNKNOWN_ERROR: 100 default: 101 return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR; 102 } 103 } 104 105 static @NonNull ArrayList<VendorKeyValue> vendorInfoToHal(@ullable Map<String, String> info)106 vendorInfoToHal(@Nullable Map<String, String> info) { 107 if (info == null) return new ArrayList<>(); 108 109 ArrayList<VendorKeyValue> list = new ArrayList<>(); 110 for (Map.Entry<String, String> entry : info.entrySet()) { 111 VendorKeyValue elem = new VendorKeyValue(); 112 elem.key = entry.getKey(); 113 elem.value = entry.getValue(); 114 if (elem.key == null || elem.value == null) { 115 Slogf.w(TAG, "VendorKeyValue contains null pointers"); 116 continue; 117 } 118 list.add(elem); 119 } 120 121 return list; 122 } 123 vendorInfoFromHal(@ullable List<VendorKeyValue> info)124 static @NonNull Map<String, String> vendorInfoFromHal(@Nullable List<VendorKeyValue> info) { 125 Map<String, String> vendorInfoMap = new ArrayMap<>(); 126 if (info == null) { 127 return vendorInfoMap; 128 } 129 130 for (VendorKeyValue kvp : info) { 131 if (kvp.key == null || kvp.value == null) { 132 Slogf.w(TAG, "VendorKeyValue contains null pointers"); 133 continue; 134 } 135 vendorInfoMap.put(kvp.key, kvp.value); 136 } 137 138 return vendorInfoMap; 139 } 140 identifierTypeToProgramType( @rogramSelector.IdentifierType int idType)141 private static @ProgramSelector.ProgramType int identifierTypeToProgramType( 142 @ProgramSelector.IdentifierType int idType) { 143 switch (idType) { 144 case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY: 145 case ProgramSelector.IDENTIFIER_TYPE_RDS_PI: 146 // TODO(b/69958423): verify AM/FM with frequency range 147 return ProgramSelector.PROGRAM_TYPE_FM; 148 case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT: 149 // TODO(b/69958423): verify AM/FM with frequency range 150 return ProgramSelector.PROGRAM_TYPE_FM_HD; 151 case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC: 152 case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE: 153 case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID: 154 case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY: 155 case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT: 156 return ProgramSelector.PROGRAM_TYPE_DAB; 157 case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID: 158 case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY: 159 return ProgramSelector.PROGRAM_TYPE_DRMO; 160 case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID: 161 case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL: 162 return ProgramSelector.PROGRAM_TYPE_SXM; 163 } 164 if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START 165 && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) { 166 return idType; 167 } 168 return ProgramSelector.PROGRAM_TYPE_INVALID; 169 } 170 171 private static @NonNull int[] identifierTypesToProgramTypes(@onNull int[] idTypes)172 identifierTypesToProgramTypes(@NonNull int[] idTypes) { 173 Set<Integer> pTypes = new ArraySet<>(); 174 175 for (int idType : idTypes) { 176 int pType = identifierTypeToProgramType(idType); 177 178 if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue; 179 180 pTypes.add(pType); 181 if (pType == ProgramSelector.PROGRAM_TYPE_FM) { 182 // TODO(b/69958423): verify AM/FM with region info 183 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM); 184 } 185 if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) { 186 // TODO(b/69958423): verify AM/FM with region info 187 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD); 188 } 189 } 190 191 return pTypes.stream().mapToInt(Integer::intValue).toArray(); 192 } 193 194 private static @NonNull RadioManager.BandDescriptor[] amfmConfigToBands(@ullable AmFmRegionConfig config)195 amfmConfigToBands(@Nullable AmFmRegionConfig config) { 196 if (config == null) return new RadioManager.BandDescriptor[0]; 197 198 int len = config.ranges.size(); 199 List<RadioManager.BandDescriptor> bands = new ArrayList<>(len); 200 201 // Just a placeholder value. 202 int region = RadioManager.REGION_ITU_1; 203 204 for (AmFmBandRange range : config.ranges) { 205 FrequencyBand bandType = Utils.getBand(range.lowerBound); 206 if (bandType == FrequencyBand.UNKNOWN) { 207 Slogf.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz"); 208 continue; 209 } 210 if (bandType == FrequencyBand.FM) { 211 bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM, 212 range.lowerBound, range.upperBound, range.spacing, 213 214 // TODO(b/69958777): stereo, rds, ta, af, ea 215 true, true, true, true, true 216 )); 217 } else { // AM 218 bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM, 219 range.lowerBound, range.upperBound, range.spacing, 220 221 // TODO(b/69958777): stereo 222 true 223 )); 224 } 225 } 226 227 return bands.toArray(new RadioManager.BandDescriptor[bands.size()]); 228 } 229 dabConfigFromHal( @ullable List<DabTableEntry> config)230 private static @Nullable Map<String, Integer> dabConfigFromHal( 231 @Nullable List<DabTableEntry> config) { 232 if (config == null) return null; 233 return config.stream().collect(Collectors.toMap(e -> e.label, e -> e.frequency)); 234 } 235 236 static @NonNull RadioManager.ModuleProperties propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop, @Nullable AmFmRegionConfig amfmConfig, @Nullable List<DabTableEntry> dabConfig)237 propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop, 238 @Nullable AmFmRegionConfig amfmConfig, @Nullable List<DabTableEntry> dabConfig) { 239 Objects.requireNonNull(serviceName); 240 Objects.requireNonNull(prop); 241 242 int[] supportedIdentifierTypes = prop.supportedIdentifierTypes.stream(). 243 mapToInt(Integer::intValue).toArray(); 244 int[] supportedProgramTypes = identifierTypesToProgramTypes(supportedIdentifierTypes); 245 246 return new RadioManager.ModuleProperties( 247 id, 248 serviceName, 249 250 // There is no Class concept in HAL 2.0. 251 RadioManager.CLASS_AM_FM, 252 253 prop.maker, 254 prop.product, 255 prop.version, 256 prop.serial, 257 258 /* HAL 2.0 only supports single tuner and audio source per 259 * HAL implementation instance. */ 260 1, // numTuners 261 1, // numAudioSources 262 false, // isInitializationRequired 263 false, // isCaptureSupported 264 265 amfmConfigToBands(amfmConfig), 266 true, // isBgScanSupported is deprecated 267 supportedProgramTypes, 268 supportedIdentifierTypes, 269 dabConfigFromHal(dabConfig), 270 vendorInfoFromHal(prop.vendorInfo) 271 ); 272 } 273 programIdentifierToHal(@onNull ProgramIdentifier hwId, @NonNull ProgramSelector.Identifier id)274 static void programIdentifierToHal(@NonNull ProgramIdentifier hwId, 275 @NonNull ProgramSelector.Identifier id) { 276 hwId.type = id.getType(); 277 hwId.value = id.getValue(); 278 } 279 programIdentifierToHal( @onNull ProgramSelector.Identifier id)280 static @NonNull ProgramIdentifier programIdentifierToHal( 281 @NonNull ProgramSelector.Identifier id) { 282 ProgramIdentifier hwId = new ProgramIdentifier(); 283 programIdentifierToHal(hwId, id); 284 return hwId; 285 } 286 programIdentifierFromHal( @onNull ProgramIdentifier id)287 static @Nullable ProgramSelector.Identifier programIdentifierFromHal( 288 @NonNull ProgramIdentifier id) { 289 if (id.type == IdentifierType.INVALID) return null; 290 return new ProgramSelector.Identifier(id.type, id.value); 291 } 292 programSelectorToHal( @onNull ProgramSelector sel)293 static @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector programSelectorToHal( 294 @NonNull ProgramSelector sel) { 295 android.hardware.broadcastradio.V2_0.ProgramSelector hwSel = 296 new android.hardware.broadcastradio.V2_0.ProgramSelector(); 297 298 programIdentifierToHal(hwSel.primaryId, sel.getPrimaryId()); 299 Arrays.stream(sel.getSecondaryIds()).map(Convert::programIdentifierToHal). 300 forEachOrdered(hwSel.secondaryIds::add); 301 302 return hwSel; 303 } 304 isEmpty( @onNull android.hardware.broadcastradio.V2_0.ProgramSelector sel)305 private static boolean isEmpty( 306 @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) { 307 return sel.primaryId.type == 0 && sel.primaryId.value == 0 && sel.secondaryIds.isEmpty(); 308 } 309 programSelectorFromHal( @onNull android.hardware.broadcastradio.V2_0.ProgramSelector sel)310 static @Nullable ProgramSelector programSelectorFromHal( 311 @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) { 312 if (isEmpty(sel)) return null; 313 314 ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream(). 315 map(Convert::programIdentifierFromHal).map(Objects::requireNonNull). 316 toArray(ProgramSelector.Identifier[]::new); 317 318 return new ProgramSelector( 319 identifierTypeToProgramType(sel.primaryId.type), 320 Objects.requireNonNull(programIdentifierFromHal(sel.primaryId)), 321 secondaryIds, /* vendorIds= */ null); 322 } 323 324 private enum MetadataType { 325 INT, STRING 326 } 327 328 private static class MetadataDef { 329 private MetadataType type; 330 private String key; MetadataDef(MetadataType type, String key)331 private MetadataDef(MetadataType type, String key) { 332 this.type = type; 333 this.key = key; 334 } 335 } 336 337 private static final SparseArray<MetadataDef> METADATA_KEYS; 338 static { 339 METADATA_KEYS = new SparseArray<>(); METADATA_KEYS.put(MetadataKey.RDS_PS, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_PS))340 METADATA_KEYS.put(MetadataKey.RDS_PS, new MetadataDef( 341 MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_PS)); METADATA_KEYS.put(MetadataKey.RDS_PTY, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_RDS_PTY))342 METADATA_KEYS.put(MetadataKey.RDS_PTY, new MetadataDef( 343 MetadataType.INT, RadioMetadata.METADATA_KEY_RDS_PTY)); METADATA_KEYS.put(MetadataKey.RBDS_PTY, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_RBDS_PTY))344 METADATA_KEYS.put(MetadataKey.RBDS_PTY, new MetadataDef( 345 MetadataType.INT, RadioMetadata.METADATA_KEY_RBDS_PTY)); METADATA_KEYS.put(MetadataKey.RDS_RT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_RT))346 METADATA_KEYS.put(MetadataKey.RDS_RT, new MetadataDef( 347 MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_RT)); METADATA_KEYS.put(MetadataKey.SONG_TITLE, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_TITLE))348 METADATA_KEYS.put(MetadataKey.SONG_TITLE, new MetadataDef( 349 MetadataType.STRING, RadioMetadata.METADATA_KEY_TITLE)); METADATA_KEYS.put(MetadataKey.SONG_ARTIST, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_ARTIST))350 METADATA_KEYS.put(MetadataKey.SONG_ARTIST, new MetadataDef( 351 MetadataType.STRING, RadioMetadata.METADATA_KEY_ARTIST)); METADATA_KEYS.put(MetadataKey.SONG_ALBUM, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_ALBUM))352 METADATA_KEYS.put(MetadataKey.SONG_ALBUM, new MetadataDef( 353 MetadataType.STRING, RadioMetadata.METADATA_KEY_ALBUM)); METADATA_KEYS.put(MetadataKey.STATION_ICON, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_ICON))354 METADATA_KEYS.put(MetadataKey.STATION_ICON, new MetadataDef( 355 MetadataType.INT, RadioMetadata.METADATA_KEY_ICON)); METADATA_KEYS.put(MetadataKey.ALBUM_ART, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_ART))356 METADATA_KEYS.put(MetadataKey.ALBUM_ART, new MetadataDef( 357 MetadataType.INT, RadioMetadata.METADATA_KEY_ART)); METADATA_KEYS.put(MetadataKey.PROGRAM_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_PROGRAM_NAME))358 METADATA_KEYS.put(MetadataKey.PROGRAM_NAME, new MetadataDef( 359 MetadataType.STRING, RadioMetadata.METADATA_KEY_PROGRAM_NAME)); METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME))360 METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef( 361 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME)); METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT))362 METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef( 363 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT)); METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME))364 METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef( 365 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME)); METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT))366 METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef( 367 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT)); METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME))368 METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef( 369 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME)); METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT))370 METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef( 371 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT)); 372 } 373 metadataFromHal(@onNull ArrayList<Metadata> meta)374 private static @NonNull RadioMetadata metadataFromHal(@NonNull ArrayList<Metadata> meta) { 375 RadioMetadata.Builder builder = new RadioMetadata.Builder(); 376 377 for (Metadata entry : meta) { 378 MetadataDef keyDef = METADATA_KEYS.get(entry.key); 379 if (keyDef == null) { 380 Slogf.i(TAG, "Ignored unknown metadata entry: " + MetadataKey.toString(entry.key)); 381 continue; 382 } 383 if (keyDef.type == MetadataType.STRING) { 384 builder.putString(keyDef.key, entry.stringValue); 385 } else { // MetadataType.INT 386 /* Current java API use 32-bit values for int metadata, 387 * but we might change it in the future */ 388 builder.putInt(keyDef.key, (int)entry.intValue); 389 } 390 } 391 392 return builder.build(); 393 } 394 programInfoFromHal(@onNull ProgramInfo info)395 static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) { 396 Collection<ProgramSelector.Identifier> relatedContent = info.relatedContent.stream(). 397 map(id -> Objects.requireNonNull(programIdentifierFromHal(id))). 398 collect(Collectors.toList()); 399 400 return new RadioManager.ProgramInfo( 401 Objects.requireNonNull(programSelectorFromHal(info.selector)), 402 programIdentifierFromHal(info.logicallyTunedTo), 403 programIdentifierFromHal(info.physicallyTunedTo), 404 relatedContent, 405 info.infoFlags, 406 info.signalQuality, 407 metadataFromHal(info.metadata), 408 vendorInfoFromHal(info.vendorInfo) 409 ); 410 } 411 programFilterToHal(@ullable ProgramList.Filter filter)412 static @NonNull ProgramFilter programFilterToHal(@Nullable ProgramList.Filter filter) { 413 if (filter == null) filter = new ProgramList.Filter(); 414 415 ProgramFilter hwFilter = new ProgramFilter(); 416 417 filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add); 418 filter.getIdentifiers().stream().forEachOrdered( 419 id -> hwFilter.identifiers.add(programIdentifierToHal(id))); 420 hwFilter.includeCategories = filter.areCategoriesIncluded(); 421 hwFilter.excludeModifications = filter.areModificationsExcluded(); 422 423 return hwFilter; 424 } 425 announcementFromHal( @onNull Announcement hwAnnouncement)426 public static @NonNull android.hardware.radio.Announcement announcementFromHal( 427 @NonNull Announcement hwAnnouncement) { 428 return new android.hardware.radio.Announcement( 429 Objects.requireNonNull(programSelectorFromHal(hwAnnouncement.selector)), 430 hwAnnouncement.type, 431 vendorInfoFromHal(hwAnnouncement.vendorInfo) 432 ); 433 } 434 listToArrayList(@ullable List<T> list)435 static <T> @Nullable ArrayList<T> listToArrayList(@Nullable List<T> list) { 436 if (list == null) return null; 437 if (list instanceof ArrayList) return (ArrayList) list; 438 return new ArrayList<>(list); 439 } 440 } 441