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