1 /* 2 * Copyright (C) 2020 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.providers.media.playlist; 18 19 import static com.android.providers.media.util.Logging.TAG; 20 21 import android.util.Log; 22 23 import androidx.annotation.NonNull; 24 25 import java.io.File; 26 import java.io.FileInputStream; 27 import java.io.FileNotFoundException; 28 import java.io.FileOutputStream; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.nio.file.InvalidPathException; 33 import java.nio.file.Path; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.Collections; 37 import java.util.List; 38 39 /** 40 * Representation of a playlist of multiple items, each represented by their 41 * {@link Path}. Note that identical items may be repeated within a playlist, 42 * and that strict ordering is maintained. 43 * <p> 44 * This representation is agnostic to file format, but you can {@link #read} 45 * playlist files into memory, modify them, and then {@link #write} them back 46 * into playlist files. This design allows you to easily convert between 47 * playlist file formats by reading one format and writing to another. 48 */ 49 public class Playlist { 50 private final ArrayList<Path> mItems = new ArrayList<>(); 51 asList()52 public List<Path> asList() { 53 return Collections.unmodifiableList(mItems); 54 } 55 clear()56 public void clear() { 57 mItems.clear(); 58 } 59 read(@onNull File file)60 public void read(@NonNull File file) throws IOException { 61 clear(); 62 try (InputStream in = new FileInputStream(file)) { 63 PlaylistPersister.resolvePersister(file).read(in, mItems); 64 } catch (FileNotFoundException e) { 65 Log.w(TAG, "Treating missing file as empty playlist"); 66 } catch (InvalidPathException e) { 67 Log.w(TAG, "Broken playlist file", e); 68 clear(); 69 } 70 } 71 write(@onNull File file)72 public void write(@NonNull File file) throws IOException { 73 try (OutputStream out = new FileOutputStream(file)) { 74 PlaylistPersister.resolvePersister(file).write(out, mItems); 75 } 76 } 77 78 /** 79 * Add the given playlist item at the nearest valid index. 80 */ add(int index, Path item)81 public int add(int index, Path item) { 82 // Gracefully handle items beyond end 83 final int size = mItems.size(); 84 index = constrain(index, 0, size); 85 86 mItems.add(index, item); 87 return index; 88 } 89 90 /** 91 * Move an existing playlist item from the nearest valid index to the 92 * nearest valid index. 93 */ move(int from, int to)94 public int move(int from, int to) { 95 // Gracefully handle items beyond end 96 final int size = mItems.size(); 97 from = constrain(from, 0, size - 1); 98 to = constrain(to, 0, size - 1); 99 100 final Path item = mItems.remove(from); 101 mItems.add(to, item); 102 return to; 103 } 104 105 /** 106 * Remove an existing playlist item from the nearest valid index. 107 */ remove(int index)108 public int remove(int index) { 109 // Gracefully handle items beyond end 110 final int size = mItems.size(); 111 index = constrain(index, 0, size - 1); 112 113 mItems.remove(index); 114 return index; 115 } 116 117 /** 118 * Removes existing playlist items that correspond to the given indexes. 119 * If an index is out of bounds, then it's ignored. 120 * 121 * @return the number of deleted items 122 */ removeMultiple(int... indexes)123 public int removeMultiple(int... indexes) { 124 int res = 0; 125 Arrays.sort(indexes); 126 127 for (int i = indexes.length - 1; i >= 0; --i) { 128 final int size = mItems.size(); 129 // Ignore items that are out of bounds 130 if (indexes[i] >=0 && indexes[i] < size) { 131 mItems.remove(indexes[i]); 132 res++; 133 } 134 } 135 136 return res; 137 } 138 constrain(int amount, int low, int high)139 private static int constrain(int amount, int low, int high) { 140 return amount < low ? low : (amount > high ? high : amount); 141 } 142 } 143