Skip to content

Commit f938062

Browse files
Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts: # app/build.gradle # app/src/main/java/org/schabi/newpipe/player/PlayerService.java # app/src/main/res/values-ca/strings.xml
2 parents da36b8a + 938265d commit f938062

93 files changed

Lines changed: 1303 additions & 322 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,15 @@ public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGrou
221221
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
222222
searchBinding = FragmentSearchBinding.bind(rootView);
223223
super.onViewCreated(rootView, savedInstanceState);
224+
225+
updateService();
226+
// Add the service name to search string hint
227+
// to make it more obvious which platform is being searched.
228+
if (service != null) {
229+
searchEditText.setHint(
230+
getString(R.string.search_with_service_name,
231+
service.getServiceInfo().getName()));
232+
}
224233
showSearchOnStart();
225234
initSearchListeners();
226235
}
@@ -942,6 +951,20 @@ private void changeContentFilter(final MenuItem item, final List<String> theCont
942951
filterItemCheckedId = item.getItemId();
943952
item.setChecked(true);
944953

954+
if (service != null) {
955+
final boolean isNotFiltered = theContentFilter.isEmpty()
956+
|| "all".equals(theContentFilter.get(0));
957+
if (isNotFiltered) {
958+
searchEditText.setHint(
959+
getString(R.string.search_with_service_name,
960+
service.getServiceInfo().getName()));
961+
} else {
962+
searchEditText.setHint(getString(R.string.search_with_service_name_and_filter,
963+
service.getServiceInfo().getName(),
964+
item.getTitle()));
965+
}
966+
}
967+
945968
contentFilter = theContentFilter.toArray(new String[0]);
946969

947970
if (!TextUtils.isEmpty(searchString)) {

app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.annotation.DrawableRes
1111
import androidx.core.net.toUri
1212
import androidx.core.os.bundleOf
1313
import androidx.media.MediaBrowserServiceCompat
14+
import androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT
1415
import androidx.media.MediaBrowserServiceCompat.Result
1516
import androidx.media.utils.MediaConstants
1617
import io.reactivex.rxjava3.core.Flowable
@@ -48,6 +49,7 @@ class MediaBrowserImpl(
4849
private val context: Context,
4950
notifyChildrenChanged: (parentId: String) -> Unit,
5051
) {
52+
private val packageValidator = PackageValidator(context)
5153
private val database = NewPipeDatabase.getInstance(context)
5254
private var disposables = CompositeDisposable()
5355

@@ -67,11 +69,22 @@ class MediaBrowserImpl(
6769
clientPackageName: String,
6870
clientUid: Int,
6971
rootHints: Bundle?
70-
): MediaBrowserServiceCompat.BrowserRoot {
72+
): MediaBrowserServiceCompat.BrowserRoot? {
7173
if (DEBUG) {
7274
Log.d(TAG, "onGetRoot($clientPackageName, $clientUid, $rootHints)")
7375
}
7476

77+
if (!packageValidator.isKnownCaller(clientPackageName, clientUid)) {
78+
// this is a caller we can't trust (see PackageValidator's rules taken from uamp)
79+
return null
80+
}
81+
82+
if (rootHints?.getBoolean(EXTRA_RECENT, false) == true) {
83+
// the system is asking for a root to do media resumption, but we can't handle that yet,
84+
// see https://developer.android.com/media/implement/surfaces/mobile#mediabrowserservice_implementation
85+
return null
86+
}
87+
7588
val extras = Bundle()
7689
extras.putBoolean(
7790
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*
2+
* Copyright 2018 Google Inc. All rights reserved.
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+
// THIS FILE WAS TAKEN FROM UAMP, EXCEPT FOR THINGS RELATED TO THE WHITELIST. UPDATE IT WHEN NEEDED.
18+
// https://github.com/android/uamp/blob/329a21b63c247e9bd35f6858d4fc0e448fa38603/common/src/main/java/com/example/android/uamp/media/PackageValidator.kt
19+
20+
package org.schabi.newpipe.player.mediabrowser
21+
22+
import android.Manifest.permission.MEDIA_CONTENT_CONTROL
23+
import android.annotation.SuppressLint
24+
import android.content.Context
25+
import android.content.pm.PackageInfo
26+
import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED
27+
import android.content.pm.PackageManager
28+
import android.os.Process
29+
import android.support.v4.media.session.MediaSessionCompat
30+
import android.util.Log
31+
import androidx.core.app.NotificationManagerCompat
32+
import androidx.media.MediaBrowserServiceCompat
33+
import org.schabi.newpipe.BuildConfig
34+
import java.security.MessageDigest
35+
import java.security.NoSuchAlgorithmException
36+
37+
/**
38+
* Validates that the calling package is authorized to browse a [MediaBrowserServiceCompat].
39+
*
40+
* The list of allowed signing certificates and their corresponding package names is defined in
41+
* res/xml/allowed_media_browser_callers.xml.
42+
*
43+
* If you want to add a new caller to allowed_media_browser_callers.xml and you don't know
44+
* its signature, this class will print to logcat (INFO level) a message with the proper
45+
* xml tags to add to allow the caller.
46+
*
47+
* For more information, see res/xml/allowed_media_browser_callers.xml.
48+
*/
49+
internal class PackageValidator(context: Context) {
50+
private val context: Context = context.applicationContext
51+
private val packageManager: PackageManager = this.context.packageManager
52+
private val platformSignature: String = getSystemSignature()
53+
private val callerChecked = mutableMapOf<String, Pair<Int, Boolean>>()
54+
55+
/**
56+
* Checks whether the caller attempting to connect to a [MediaBrowserServiceCompat] is known.
57+
* See [MusicService.onGetRoot] for where this is utilized.
58+
*
59+
* @param callingPackage The package name of the caller.
60+
* @param callingUid The user id of the caller.
61+
* @return `true` if the caller is known, `false` otherwise.
62+
*/
63+
fun isKnownCaller(callingPackage: String, callingUid: Int): Boolean {
64+
// If the caller has already been checked, return the previous result here.
65+
val (checkedUid, checkResult) = callerChecked[callingPackage] ?: Pair(0, false)
66+
if (checkedUid == callingUid) {
67+
return checkResult
68+
}
69+
70+
/**
71+
* Because some of these checks can be slow, we save the results in [callerChecked] after
72+
* this code is run.
73+
*
74+
* In particular, there's little reason to recompute the calling package's certificate
75+
* signature (SHA-256) each call.
76+
*
77+
* This is safe to do as we know the UID matches the package's UID (from the check above),
78+
* and app UIDs are set at install time. Additionally, a package name + UID is guaranteed to
79+
* be constant until a reboot. (After a reboot then a previously assigned UID could be
80+
* reassigned.)
81+
*/
82+
83+
// Build the caller info for the rest of the checks here.
84+
val callerPackageInfo = buildCallerInfo(callingPackage)
85+
?: throw IllegalStateException("Caller wasn't found in the system?")
86+
87+
// Verify that things aren't ... broken. (This test should always pass.)
88+
if (callerPackageInfo.uid != callingUid) {
89+
throw IllegalStateException("Caller's package UID doesn't match caller's actual UID?")
90+
}
91+
92+
val callerSignature = callerPackageInfo.signature
93+
94+
val isCallerKnown = when {
95+
// If it's our own app making the call, allow it.
96+
callingUid == Process.myUid() -> true
97+
// If the system is making the call, allow it.
98+
callingUid == Process.SYSTEM_UID -> true
99+
// If the app was signed by the same certificate as the platform itself, also allow it.
100+
callerSignature == platformSignature -> true
101+
/**
102+
* [MEDIA_CONTENT_CONTROL] permission is only available to system applications, and
103+
* while it isn't required to allow these apps to connect to a
104+
* [MediaBrowserServiceCompat], allowing this ensures optimal compatability with apps
105+
* such as Android TV and the Google Assistant.
106+
*/
107+
callerPackageInfo.permissions.contains(MEDIA_CONTENT_CONTROL) -> true
108+
/**
109+
* If the calling app has a notification listener it is able to retrieve notifications
110+
* and can connect to an active [MediaSessionCompat].
111+
*
112+
* It's not required to allow apps with a notification listener to
113+
* connect to your [MediaBrowserServiceCompat], but it does allow easy compatibility
114+
* with apps such as Wear OS.
115+
*/
116+
NotificationManagerCompat.getEnabledListenerPackages(this.context)
117+
.contains(callerPackageInfo.packageName) -> true
118+
119+
// If none of the previous checks succeeded, then the caller is unrecognized.
120+
else -> false
121+
}
122+
123+
if (!isCallerKnown) {
124+
logUnknownCaller(callerPackageInfo)
125+
}
126+
127+
// Save our work for next time.
128+
callerChecked[callingPackage] = Pair(callingUid, isCallerKnown)
129+
return isCallerKnown
130+
}
131+
132+
/**
133+
* Logs an info level message with details of how to add a caller to the allowed callers list
134+
* when the app is debuggable.
135+
*/
136+
private fun logUnknownCaller(callerPackageInfo: CallerPackageInfo) {
137+
if (BuildConfig.DEBUG) {
138+
Log.w(TAG, "Unknown caller $callerPackageInfo")
139+
}
140+
}
141+
142+
/**
143+
* Builds a [CallerPackageInfo] for a given package that can be used for all the
144+
* various checks that are performed before allowing an app to connect to a
145+
* [MediaBrowserServiceCompat].
146+
*/
147+
private fun buildCallerInfo(callingPackage: String): CallerPackageInfo? {
148+
val packageInfo = getPackageInfo(callingPackage) ?: return null
149+
150+
val appName = packageInfo.applicationInfo.loadLabel(packageManager).toString()
151+
val uid = packageInfo.applicationInfo.uid
152+
val signature = getSignature(packageInfo)
153+
154+
val requestedPermissions = packageInfo.requestedPermissions
155+
val permissionFlags = packageInfo.requestedPermissionsFlags
156+
val activePermissions = mutableSetOf<String>()
157+
requestedPermissions?.forEachIndexed { index, permission ->
158+
if (permissionFlags[index] and REQUESTED_PERMISSION_GRANTED != 0) {
159+
activePermissions += permission
160+
}
161+
}
162+
163+
return CallerPackageInfo(appName, callingPackage, uid, signature, activePermissions.toSet())
164+
}
165+
166+
/**
167+
* Looks up the [PackageInfo] for a package name.
168+
* This requests both the signatures (for checking if an app is on the allow list) and
169+
* the app's permissions, which allow for more flexibility in the allow list.
170+
*
171+
* @return [PackageInfo] for the package name or null if it's not found.
172+
*/
173+
@Suppress("deprecation")
174+
@SuppressLint("PackageManagerGetSignatures")
175+
private fun getPackageInfo(callingPackage: String): PackageInfo? =
176+
packageManager.getPackageInfo(
177+
callingPackage,
178+
PackageManager.GET_SIGNATURES or PackageManager.GET_PERMISSIONS
179+
)
180+
181+
/**
182+
* Gets the signature of a given package's [PackageInfo].
183+
*
184+
* The "signature" is a SHA-256 hash of the public key of the signing certificate used by
185+
* the app.
186+
*
187+
* If the app is not found, or if the app does not have exactly one signature, this method
188+
* returns `null` as the signature.
189+
*/
190+
@Suppress("deprecation")
191+
private fun getSignature(packageInfo: PackageInfo): String? =
192+
if (packageInfo.signatures == null || packageInfo.signatures.size != 1) {
193+
// Security best practices dictate that an app should be signed with exactly one (1)
194+
// signature. Because of this, if there are multiple signatures, reject it.
195+
null
196+
} else {
197+
val certificate = packageInfo.signatures[0].toByteArray()
198+
getSignatureSha256(certificate)
199+
}
200+
201+
/**
202+
* Finds the Android platform signing key signature. This key is never null.
203+
*/
204+
private fun getSystemSignature(): String =
205+
getPackageInfo(ANDROID_PLATFORM)?.let { platformInfo ->
206+
getSignature(platformInfo)
207+
} ?: throw IllegalStateException("Platform signature not found")
208+
209+
/**
210+
* Creates a SHA-256 signature given a certificate byte array.
211+
*/
212+
private fun getSignatureSha256(certificate: ByteArray): String {
213+
val md: MessageDigest
214+
try {
215+
md = MessageDigest.getInstance("SHA256")
216+
} catch (noSuchAlgorithmException: NoSuchAlgorithmException) {
217+
Log.e(TAG, "No such algorithm: $noSuchAlgorithmException")
218+
throw RuntimeException("Could not find SHA256 hash algorithm", noSuchAlgorithmException)
219+
}
220+
md.update(certificate)
221+
222+
// This code takes the byte array generated by `md.digest()` and joins each of the bytes
223+
// to a string, applying the string format `%02x` on each digit before it's appended, with
224+
// a colon (':') between each of the items.
225+
// For example: input=[0,2,4,6,8,10,12], output="00:02:04:06:08:0a:0c"
226+
return md.digest().joinToString(":") { String.format("%02x", it) }
227+
}
228+
229+
/**
230+
* Convenience class to hold all of the information about an app that's being checked
231+
* to see if it's a known caller.
232+
*/
233+
private data class CallerPackageInfo(
234+
val name: String,
235+
val packageName: String,
236+
val uid: Int,
237+
val signature: String?,
238+
val permissions: Set<String>
239+
)
240+
}
241+
242+
private const val TAG = "PackageValidator"
243+
private const val ANDROID_PLATFORM = "android"

app/src/main/java/org/schabi/newpipe/util/ChannelTabHelper.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public static boolean isStreamsTab(final String tab) {
2424
switch (tab) {
2525
case ChannelTabs.VIDEOS:
2626
case ChannelTabs.TRACKS:
27+
case ChannelTabs.LIKES:
2728
case ChannelTabs.SHORTS:
2829
case ChannelTabs.LIVESTREAMS:
2930
return true;
@@ -62,6 +63,8 @@ private static int getShowTabKey(final String tab) {
6263
return R.string.show_channel_tabs_playlists;
6364
case ChannelTabs.ALBUMS:
6465
return R.string.show_channel_tabs_albums;
66+
case ChannelTabs.LIKES:
67+
return R.string.show_channel_tabs_likes;
6568
default:
6669
return -1;
6770
}
@@ -78,6 +81,8 @@ private static int getFetchFeedTabKey(final String tab) {
7881
return R.string.fetch_channel_tabs_shorts;
7982
case ChannelTabs.LIVESTREAMS:
8083
return R.string.fetch_channel_tabs_livestreams;
84+
case ChannelTabs.LIKES:
85+
return R.string.fetch_channel_tabs_likes;
8186
default:
8287
return -1;
8388
}
@@ -100,6 +105,8 @@ public static int getTranslationKey(final String tab) {
100105
return R.string.channel_tab_playlists;
101106
case ChannelTabs.ALBUMS:
102107
return R.string.channel_tab_albums;
108+
case ChannelTabs.LIKES:
109+
return R.string.channel_tab_likes;
103110
default:
104111
return R.string.unknown_content;
105112
}

app/src/main/res/values-ar/strings.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
<string name="show_play_with_kodi_summary">اعرض خيار لتشغيل الفيديو عبر مركز وسائط Kodi</string>
3838
<string name="show_play_with_kodi_title">عرض خيار التشغيل بواسطة كودي</string>
3939
<string name="theme_title">السمة</string>
40-
<string name="upload_date_text">تم النشر في %1$s</string>
40+
<string name="upload_date_text">منشورة على %1$s</string>
4141
<string name="unsupported_url">رابط غير مدعوم</string>
4242
<string name="use_external_audio_player_title">استخدام مشغل صوت خارجي</string>
4343
<string name="use_external_video_player_title">استخدام مشغل فيديو خارجي</string>
@@ -879,4 +879,9 @@
879879
<string name="no">لا</string>
880880
<string name="import_settings_vulnerable_format">تستخدم الإعدادات الموجودة في عملية التصدير التي يتم استيرادها تنسيقًا عرضة للاختراق تم إهماله منذ NewPipe 0.27.0. تأكد من أن التصدير الذي يتم استيراده من مصدر موثوق به، ويفضل استخدام عمليات التصدير التي تم الحصول عليها من NewPipe 0.27.0 أو الأحدث في المستقبل فقط. سيتم قريبًا إزالة دعم استيراد الإعدادات بهذا التنسيق الضعيف تمامًا، وبعد ذلك لن تتمكن الإصدارات القديمة من NewPipe من استيراد إعدادات التصدير من الإصدارات الجديدة بعد الآن.</string>
881881
<string name="audio_track_type_secondary">الثانوي</string>
882+
<string name="share_playlist_as_youtube_temporary_playlist">المشاركة كقائمة تشغيل مؤقتة على YouTube</string>
883+
<string name="tab_bookmarks_short">قوائم التشغيل</string>
884+
<string name="feed_group_page_summary">صفحة مجموعة القناة</string>
885+
<string name="select_a_feed_group">حدد مجموعة المحتوى</string>
886+
<string name="no_feed_group_created_yet">لم تنشئ مجموعة محتوى</string>
882887
</resources>

app/src/main/res/values-az/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,4 +801,9 @@
801801
<string name="image_quality_summary">Məlumat və yaddaş istifadəsini azaltmaq üçün şəkillərin keyfiyyətini və ya şəkillərin əsla yüklənib-yüklənilməməsini seçin. Dəyişikliklər həm yaddaşdaxili, həm də diskdə olan təsvir qalığın təmizləyir — %s</string>
802802
<string name="share_playlist_with_list">URL siyahısını paylaşın</string>
803803
<string name="audio_track_type_secondary">ikinci dərəcəli</string>
804+
<string name="share_playlist_as_youtube_temporary_playlist">YouTube müvəqqəti pleylisti kimi paylaş</string>
805+
<string name="tab_bookmarks_short">Pleylistlər</string>
806+
<string name="select_a_feed_group">Axın qrupu seçin</string>
807+
<string name="no_feed_group_created_yet">Hələ heç bir axın qrupu yaradılmayıb</string>
808+
<string name="feed_group_page_summary">Kanal qrupu səhifəsi</string>
804809
</resources>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
</resources>

0 commit comments

Comments
 (0)