Skip to content

Commit 231e677

Browse files
authored
Merge pull request TeamNewPipe#8895 from Isira-Seneviratne/SparseArrayCompat
Use SparseArrayCompat.
2 parents fcac53c + d661700 commit 231e677

6 files changed

Lines changed: 86 additions & 139 deletions

File tree

app/src/androidTest/java/org/schabi/newpipe/util/StreamItemAdapterTest.kt

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package org.schabi.newpipe.util
22

33
import android.content.Context
4-
import android.util.SparseArray
54
import android.view.View
65
import android.view.View.GONE
76
import android.view.View.INVISIBLE
87
import android.view.View.VISIBLE
98
import android.widget.Spinner
9+
import androidx.collection.SparseArrayCompat
1010
import androidx.test.core.app.ApplicationProvider
1111
import androidx.test.ext.junit.runners.AndroidJUnit4
1212
import androidx.test.filters.MediumTest
@@ -39,9 +39,7 @@ class StreamItemAdapterTest {
3939
@Test
4040
fun videoStreams_noSecondaryStream() {
4141
val adapter = StreamItemAdapter<VideoStream, AudioStream>(
42-
context,
43-
getVideoStreams(true, true, true, true),
44-
null
42+
getVideoStreams(true, true, true, true)
4543
)
4644

4745
spinner.adapter = adapter
@@ -54,7 +52,6 @@ class StreamItemAdapterTest {
5452
@Test
5553
fun videoStreams_hasSecondaryStream() {
5654
val adapter = StreamItemAdapter(
57-
context,
5855
getVideoStreams(false, true, false, true),
5956
getAudioStreams(false, true, false, true)
6057
)
@@ -69,7 +66,6 @@ class StreamItemAdapterTest {
6966
@Test
7067
fun videoStreams_Mixed() {
7168
val adapter = StreamItemAdapter(
72-
context,
7369
getVideoStreams(true, true, true, true, true, false, true, true),
7470
getAudioStreams(false, true, false, false, false, true, true, true)
7571
)
@@ -88,7 +84,6 @@ class StreamItemAdapterTest {
8884
@Test
8985
fun subtitleStreams_noIcon() {
9086
val adapter = StreamItemAdapter<SubtitlesStream, Stream>(
91-
context,
9287
StreamItemAdapter.StreamSizeWrapper(
9388
(0 until 5).map {
9489
SubtitlesStream.Builder()
@@ -99,8 +94,7 @@ class StreamItemAdapterTest {
9994
.build()
10095
},
10196
context
102-
),
103-
null
97+
)
10498
)
10599
spinner.adapter = adapter
106100
for (i in 0 until spinner.count) {
@@ -111,7 +105,6 @@ class StreamItemAdapterTest {
111105
@Test
112106
fun audioStreams_noIcon() {
113107
val adapter = StreamItemAdapter<AudioStream, Stream>(
114-
context,
115108
StreamItemAdapter.StreamSizeWrapper(
116109
(0 until 5).map {
117110
AudioStream.Builder()
@@ -122,8 +115,7 @@ class StreamItemAdapterTest {
122115
.build()
123116
},
124117
context
125-
),
126-
null
118+
)
127119
)
128120
spinner.adapter = adapter
129121
for (i in 0 until spinner.count) {
@@ -200,7 +192,7 @@ class StreamItemAdapterTest {
200192
* Helper function that builds a secondary stream list.
201193
*/
202194
private fun <T : Stream> getSecondaryStreamsFromList(streams: List<T?>) =
203-
SparseArray<SecondaryStreamHelper<T>?>(streams.size).apply {
195+
SparseArrayCompat<SecondaryStreamHelper<T>?>(streams.size).apply {
204196
streams.forEachIndexed { index, stream ->
205197
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
206198
SecondaryStreamHelper(

app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import android.os.Environment;
1818
import android.os.IBinder;
1919
import android.util.Log;
20-
import android.util.SparseArray;
2120
import android.view.LayoutInflater;
2221
import android.view.View;
2322
import android.view.ViewGroup;
@@ -36,6 +35,7 @@
3635
import androidx.appcompat.app.AlertDialog;
3736
import androidx.appcompat.view.menu.ActionMenuItemView;
3837
import androidx.appcompat.widget.Toolbar;
38+
import androidx.collection.SparseArrayCompat;
3939
import androidx.documentfile.provider.DocumentFile;
4040
import androidx.fragment.app.DialogFragment;
4141
import androidx.preference.PreferenceManager;
@@ -211,8 +211,7 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
211211
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
212212
Icepick.restoreInstanceState(this, savedInstanceState);
213213

214-
final SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams =
215-
new SparseArray<>(4);
214+
final var secondaryStreams = new SparseArrayCompat<SecondaryStreamHelper<AudioStream>>(4);
216215
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
217216

218217
for (int i = 0; i < videoStreams.size(); i++) {
@@ -236,10 +235,9 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
236235
}
237236
}
238237

239-
this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams,
240-
secondaryStreams);
241-
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
242-
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
238+
this.videoStreamsAdapter = new StreamItemAdapter<>(wrappedVideoStreams, secondaryStreams);
239+
this.audioStreamsAdapter = new StreamItemAdapter<>(wrappedAudioStreams);
240+
this.subtitleStreamsAdapter = new StreamItemAdapter<>(wrappedSubtitleStreams);
243241

244242
final Intent intent = new Intent(context, DownloadManagerService.class);
245243
context.startService(intent);

app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import androidx.appcompat.app.ActionBar;
3434
import androidx.appcompat.app.AlertDialog;
3535
import androidx.appcompat.widget.TooltipCompat;
36+
import androidx.collection.SparseArrayCompat;
3637
import androidx.core.text.HtmlCompat;
3738
import androidx.preference.PreferenceManager;
3839
import androidx.recyclerview.widget.ItemTouchHelper;
@@ -70,9 +71,7 @@
7071
import java.util.ArrayList;
7172
import java.util.Arrays;
7273
import java.util.Collections;
73-
import java.util.HashMap;
7474
import java.util.List;
75-
import java.util.Map;
7675
import java.util.Queue;
7776
import java.util.concurrent.TimeUnit;
7877
import java.util.stream.Collectors;
@@ -141,7 +140,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
141140
@State
142141
boolean wasSearchFocused = false;
143142

144-
@Nullable private Map<Integer, String> menuItemToFilterName = null;
143+
private final SparseArrayCompat<String> menuItemToFilterName = new SparseArrayCompat<>();
145144
private StreamingService service;
146145
private Page nextPage;
147146
private boolean showLocalSuggestions = true;
@@ -426,8 +425,6 @@ public void onCreateOptionsMenu(@NonNull final Menu menu,
426425
supportActionBar.setDisplayHomeAsUpEnabled(true);
427426
}
428427

429-
menuItemToFilterName = new HashMap<>();
430-
431428
int itemId = 0;
432429
boolean isFirstItem = true;
433430
final Context c = getContext();
@@ -468,11 +465,8 @@ public void onCreateOptionsMenu(@NonNull final Menu menu,
468465

469466
@Override
470467
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
471-
if (menuItemToFilterName != null) {
472-
final List<String> cf = new ArrayList<>(1);
473-
cf.add(menuItemToFilterName.get(item.getItemId()));
474-
changeContentFilter(item, cf);
475-
}
468+
final var filter = Collections.singletonList(menuItemToFilterName.get(item.getItemId()));
469+
changeContentFilter(item, filter);
476470
return true;
477471
}
478472

app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java

Lines changed: 37 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
package org.schabi.newpipe.player.seekbarpreview;
22

33
import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType;
4+
import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.getSeekbarPreviewThumbnailType;
45

56
import android.content.Context;
67
import android.graphics.Bitmap;
78
import android.util.Log;
89

910
import androidx.annotation.NonNull;
1011
import androidx.annotation.Nullable;
12+
import androidx.collection.SparseArrayCompat;
1113

1214
import com.google.common.base.Stopwatch;
1315

1416
import org.schabi.newpipe.extractor.stream.Frameset;
1517
import org.schabi.newpipe.util.PicassoHelper;
1618

1719
import java.util.Comparator;
18-
import java.util.HashMap;
1920
import java.util.List;
20-
import java.util.Map;
2121
import java.util.Optional;
2222
import java.util.UUID;
23-
import java.util.concurrent.ConcurrentHashMap;
2423
import java.util.concurrent.ExecutorService;
2524
import java.util.concurrent.Executors;
2625
import java.util.function.Supplier;
@@ -34,18 +33,15 @@ public class SeekbarPreviewThumbnailHolder {
3433

3534
// Key = Position of the picture in milliseconds
3635
// Supplier = Supplies the bitmap for that position
37-
private final Map<Integer, Supplier<Bitmap>> seekbarPreviewData = new ConcurrentHashMap<>();
36+
private final SparseArrayCompat<Supplier<Bitmap>> seekbarPreviewData =
37+
new SparseArrayCompat<>();
3838

3939
// This ensures that if the reset is still undergoing
4040
// and another reset starts, only the last reset is processed
4141
private UUID currentUpdateRequestIdentifier = UUID.randomUUID();
4242

43-
public synchronized void resetFrom(
44-
@NonNull final Context context,
45-
final List<Frameset> framesets) {
46-
47-
final int seekbarPreviewType =
48-
SeekbarPreviewThumbnailHelper.getSeekbarPreviewThumbnailType(context);
43+
public void resetFrom(@NonNull final Context context, final List<Frameset> framesets) {
44+
final int seekbarPreviewType = getSeekbarPreviewThumbnailType(context);
4945

5046
final UUID updateRequestIdentifier = UUID.randomUUID();
5147
this.currentUpdateRequestIdentifier = updateRequestIdentifier;
@@ -63,13 +59,12 @@ public synchronized void resetFrom(
6359
executorService.shutdown();
6460
}
6561

66-
private void resetFromAsync(
67-
final int seekbarPreviewType,
68-
final List<Frameset> framesets,
69-
final UUID updateRequestIdentifier) {
70-
62+
private void resetFromAsync(final int seekbarPreviewType, final List<Frameset> framesets,
63+
final UUID updateRequestIdentifier) {
7164
Log.d(TAG, "Clearing seekbarPreviewData");
72-
seekbarPreviewData.clear();
65+
synchronized (seekbarPreviewData) {
66+
seekbarPreviewData.clear();
67+
}
7368

7469
if (seekbarPreviewType == SeekbarPreviewThumbnailType.NONE) {
7570
Log.d(TAG, "Not processing seekbarPreviewData due to settings");
@@ -94,10 +89,8 @@ private void resetFromAsync(
9489
generateDataFrom(frameset, updateRequestIdentifier);
9590
}
9691

97-
private Frameset getFrameSetForType(
98-
final List<Frameset> framesets,
99-
final int seekbarPreviewType) {
100-
92+
private Frameset getFrameSetForType(final List<Frameset> framesets,
93+
final int seekbarPreviewType) {
10194
if (seekbarPreviewType == SeekbarPreviewThumbnailType.HIGH_QUALITY) {
10295
Log.d(TAG, "Strategy for seekbarPreviewData: high quality");
10396
return framesets.stream()
@@ -111,17 +104,14 @@ private Frameset getFrameSetForType(
111104
}
112105
}
113106

114-
private void generateDataFrom(
115-
final Frameset frameset,
116-
final UUID updateRequestIdentifier) {
117-
107+
private void generateDataFrom(final Frameset frameset, final UUID updateRequestIdentifier) {
118108
Log.d(TAG, "Starting generation of seekbarPreviewData");
119109
final Stopwatch sw = Log.isLoggable(TAG, Log.DEBUG) ? Stopwatch.createStarted() : null;
120110

121111
int currentPosMs = 0;
122112
int pos = 1;
123113

124-
final int frameCountPerUrl = frameset.getFramesPerPageX() * frameset.getFramesPerPageY();
114+
final int urlFrameCount = frameset.getFramesPerPageX() * frameset.getFramesPerPageY();
125115

126116
// Process each url in the frameset
127117
for (final String url : frameset.getUrls()) {
@@ -130,11 +120,11 @@ private void generateDataFrom(
130120

131121
// The data is not added directly to "seekbarPreviewData" due to
132122
// concurrency and checks for "updateRequestIdentifier"
133-
final Map<Integer, Supplier<Bitmap>> generatedDataForUrl = new HashMap<>();
123+
final var generatedDataForUrl = new SparseArrayCompat<Supplier<Bitmap>>(urlFrameCount);
134124

135125
// The bitmap consists of several images, which we process here
136126
// foreach frame in the returned bitmap
137-
for (int i = 0; i < frameCountPerUrl; i++) {
127+
for (int i = 0; i < urlFrameCount; i++) {
138128
// Frames outside the video length are skipped
139129
if (pos > frameset.getTotalCount()) {
140130
break;
@@ -161,15 +151,17 @@ private void generateDataFrom(
161151
// Check if we are still the latest request
162152
// If not abort method execution
163153
if (isRequestIdentifierCurrent(updateRequestIdentifier)) {
164-
seekbarPreviewData.putAll(generatedDataForUrl);
154+
synchronized (seekbarPreviewData) {
155+
seekbarPreviewData.putAll(generatedDataForUrl);
156+
}
165157
} else {
166158
Log.d(TAG, "Aborted of generation of seekbarPreviewData");
167159
break;
168160
}
169161
}
170162

171163
if (sw != null) {
172-
Log.d(TAG, "Generation of seekbarPreviewData took " + sw.stop().toString());
164+
Log.d(TAG, "Generation of seekbarPreviewData took " + sw.stop());
173165
}
174166
}
175167

@@ -189,17 +181,14 @@ private Bitmap getBitMapFrom(final String url) {
189181
final Bitmap bitmap = PicassoHelper.loadSeekbarThumbnailPreview(url).get();
190182

191183
if (sw != null) {
192-
Log.d(TAG,
193-
"Download of bitmap for seekbarPreview from '" + url
194-
+ "' took " + sw.stop().toString());
184+
Log.d(TAG, "Download of bitmap for seekbarPreview from '" + url + "' took "
185+
+ sw.stop());
195186
}
196187

197188
return bitmap;
198189
} catch (final Exception ex) {
199-
Log.w(TAG,
200-
"Failed to get bitmap for seekbarPreview from url='" + url
201-
+ "' in time",
202-
ex);
190+
Log.w(TAG, "Failed to get bitmap for seekbarPreview from url='" + url
191+
+ "' in time", ex);
203192
return null;
204193
}
205194
}
@@ -208,32 +197,20 @@ private boolean isRequestIdentifierCurrent(final UUID requestIdentifier) {
208197
return this.currentUpdateRequestIdentifier.equals(requestIdentifier);
209198
}
210199

211-
212200
public Optional<Bitmap> getBitmapAt(final int positionInMs) {
213-
// Check if the BitmapData is empty
214-
if (seekbarPreviewData.isEmpty()) {
215-
return Optional.empty();
216-
}
217-
218-
// Get the closest frame to the requested position
219-
final int closestIndexPosition =
220-
seekbarPreviewData.keySet().stream()
221-
.min(Comparator.comparingInt(i -> Math.abs(i - positionInMs)))
222-
.orElse(-1);
223-
224-
// this should never happen, because
225-
// it indicates that "seekbarPreviewData" is empty which was already checked
226-
if (closestIndexPosition == -1) {
227-
return Optional.empty();
201+
// Get the frame supplier closest to the requested position
202+
Supplier<Bitmap> closestFrame = () -> null;
203+
synchronized (seekbarPreviewData) {
204+
int min = Integer.MAX_VALUE;
205+
for (int i = 0; i < seekbarPreviewData.size(); i++) {
206+
final int pos = Math.abs(seekbarPreviewData.keyAt(i) - positionInMs);
207+
if (pos < min) {
208+
closestFrame = seekbarPreviewData.valueAt(i);
209+
min = pos;
210+
}
211+
}
228212
}
229213

230-
try {
231-
// Get the bitmap for the position (executes the supplier)
232-
return Optional.ofNullable(seekbarPreviewData.get(closestIndexPosition).get());
233-
} catch (final Exception ex) {
234-
// If there is an error, log it and return Optional.empty
235-
Log.w(TAG, "Unable to get seekbar preview", ex);
236-
return Optional.empty();
237-
}
214+
return Optional.ofNullable(closestFrame.get());
238215
}
239216
}

0 commit comments

Comments
 (0)