Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
496a315d
authored
May 29, 2020
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #7395 from jdegroot-dss:add-storage-not-low-requirement
PiperOrigin-RevId: 313804207
parents
235df090
2af9b4b0
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
132 additions
and
11 deletions
RELEASENOTES.md
extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java
extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java
library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java
library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java
library/core/src/main/java/com/google/android/exoplayer2/scheduler/Scheduler.java
RELEASENOTES.md
View file @
496a315d
...
@@ -142,6 +142,8 @@
...
@@ -142,6 +142,8 @@
directly instead.
directly instead.
*
Update
`CachedContentIndex`
to use
`SecureRandom`
for generating the
*
Update
`CachedContentIndex`
to use
`SecureRandom`
for generating the
initialization vector used to encrypt the cache contents.
initialization vector used to encrypt the cache contents.
*
Add
`Requirements.DEVICE_STORAGE_NOT_LOW`
, which can be specified as a
requirement to a
`DownloadManager`
for it to proceed with downloading.
*
Audio:
*
Audio:
*
Add a sample count parameter to
`MediaCodecRenderer.processOutputBuffer`
*
Add a sample count parameter to
`MediaCodecRenderer.processOutputBuffer`
and
`AudioSink.handleBuffer`
to allow batching multiple encoded frames
and
`AudioSink.handleBuffer`
to allow batching multiple encoded frames
...
...
extensions/jobdispatcher/src/main/java/com/google/android/exoplayer2/ext/jobdispatcher/JobDispatcherScheduler.java
View file @
496a315d
...
@@ -65,6 +65,11 @@ public final class JobDispatcherScheduler implements Scheduler {
...
@@ -65,6 +65,11 @@ public final class JobDispatcherScheduler implements Scheduler {
private
static
final
String
KEY_SERVICE_ACTION
=
"service_action"
;
private
static
final
String
KEY_SERVICE_ACTION
=
"service_action"
;
private
static
final
String
KEY_SERVICE_PACKAGE
=
"service_package"
;
private
static
final
String
KEY_SERVICE_PACKAGE
=
"service_package"
;
private
static
final
String
KEY_REQUIREMENTS
=
"requirements"
;
private
static
final
String
KEY_REQUIREMENTS
=
"requirements"
;
private
static
final
int
SUPPORTED_REQUIREMENTS
=
Requirements
.
NETWORK
|
Requirements
.
NETWORK_UNMETERED
|
Requirements
.
DEVICE_IDLE
|
Requirements
.
DEVICE_CHARGING
;
private
final
String
jobTag
;
private
final
String
jobTag
;
private
final
FirebaseJobDispatcher
jobDispatcher
;
private
final
FirebaseJobDispatcher
jobDispatcher
;
...
@@ -96,24 +101,35 @@ public final class JobDispatcherScheduler implements Scheduler {
...
@@ -96,24 +101,35 @@ public final class JobDispatcherScheduler implements Scheduler {
return
result
==
FirebaseJobDispatcher
.
CANCEL_RESULT_SUCCESS
;
return
result
==
FirebaseJobDispatcher
.
CANCEL_RESULT_SUCCESS
;
}
}
@Override
public
Requirements
getSupportedRequirements
(
Requirements
requirements
)
{
return
requirements
.
filterRequirements
(
SUPPORTED_REQUIREMENTS
);
}
private
static
Job
buildJob
(
private
static
Job
buildJob
(
FirebaseJobDispatcher
dispatcher
,
FirebaseJobDispatcher
dispatcher
,
Requirements
requirements
,
Requirements
requirements
,
String
tag
,
String
tag
,
String
servicePackage
,
String
servicePackage
,
String
serviceAction
)
{
String
serviceAction
)
{
Requirements
filteredRequirements
=
requirements
.
filterRequirements
(
SUPPORTED_REQUIREMENTS
);
if
(!
filteredRequirements
.
equals
(
requirements
))
{
Log
.
w
(
TAG
,
"Ignoring unsupported requirements: "
+
(
filteredRequirements
.
getRequirements
()
^
requirements
.
getRequirements
()));
}
Job
.
Builder
builder
=
Job
.
Builder
builder
=
dispatcher
dispatcher
.
newJobBuilder
()
.
newJobBuilder
()
.
setService
(
JobDispatcherSchedulerService
.
class
)
// the JobService that will be called
.
setService
(
JobDispatcherSchedulerService
.
class
)
// the JobService that will be called
.
setTag
(
tag
);
.
setTag
(
tag
);
if
(
requirements
.
isUnmeteredNetworkRequired
())
{
if
(
requirements
.
isUnmeteredNetworkRequired
())
{
builder
.
addConstraint
(
Constraint
.
ON_UNMETERED_NETWORK
);
builder
.
addConstraint
(
Constraint
.
ON_UNMETERED_NETWORK
);
}
else
if
(
requirements
.
isNetworkRequired
())
{
}
else
if
(
requirements
.
isNetworkRequired
())
{
builder
.
addConstraint
(
Constraint
.
ON_ANY_NETWORK
);
builder
.
addConstraint
(
Constraint
.
ON_ANY_NETWORK
);
}
}
if
(
requirements
.
isIdleRequired
())
{
if
(
requirements
.
isIdleRequired
())
{
builder
.
addConstraint
(
Constraint
.
DEVICE_IDLE
);
builder
.
addConstraint
(
Constraint
.
DEVICE_IDLE
);
}
}
...
...
extensions/workmanager/src/main/java/com/google/android/exoplayer2/ext/workmanager/WorkManagerScheduler.java
View file @
496a315d
...
@@ -40,6 +40,12 @@ public final class WorkManagerScheduler implements Scheduler {
...
@@ -40,6 +40,12 @@ public final class WorkManagerScheduler implements Scheduler {
private
static
final
String
KEY_SERVICE_ACTION
=
"service_action"
;
private
static
final
String
KEY_SERVICE_ACTION
=
"service_action"
;
private
static
final
String
KEY_SERVICE_PACKAGE
=
"service_package"
;
private
static
final
String
KEY_SERVICE_PACKAGE
=
"service_package"
;
private
static
final
String
KEY_REQUIREMENTS
=
"requirements"
;
private
static
final
String
KEY_REQUIREMENTS
=
"requirements"
;
private
static
final
int
SUPPORTED_REQUIREMENTS
=
Requirements
.
NETWORK
|
Requirements
.
NETWORK_UNMETERED
|
(
Util
.
SDK_INT
>=
23
?
Requirements
.
DEVICE_IDLE
:
0
)
|
Requirements
.
DEVICE_CHARGING
|
Requirements
.
DEVICE_STORAGE_NOT_LOW
;
private
final
String
workName
;
private
final
String
workName
;
...
@@ -70,9 +76,21 @@ public final class WorkManagerScheduler implements Scheduler {
...
@@ -70,9 +76,21 @@ public final class WorkManagerScheduler implements Scheduler {
return
true
;
return
true
;
}
}
@Override
public
Requirements
getSupportedRequirements
(
Requirements
requirements
)
{
return
requirements
.
filterRequirements
(
SUPPORTED_REQUIREMENTS
);
}
private
static
Constraints
buildConstraints
(
Requirements
requirements
)
{
private
static
Constraints
buildConstraints
(
Requirements
requirements
)
{
Constraints
.
Builder
builder
=
new
Constraints
.
Builder
();
Requirements
filteredRequirements
=
requirements
.
filterRequirements
(
SUPPORTED_REQUIREMENTS
);
if
(!
filteredRequirements
.
equals
(
requirements
))
{
Log
.
w
(
TAG
,
"Ignoring unsupported requirements: "
+
(
filteredRequirements
.
getRequirements
()
^
requirements
.
getRequirements
()));
}
Constraints
.
Builder
builder
=
new
Constraints
.
Builder
();
if
(
requirements
.
isUnmeteredNetworkRequired
())
{
if
(
requirements
.
isUnmeteredNetworkRequired
())
{
builder
.
setRequiredNetworkType
(
NetworkType
.
UNMETERED
);
builder
.
setRequiredNetworkType
(
NetworkType
.
UNMETERED
);
}
else
if
(
requirements
.
isNetworkRequired
())
{
}
else
if
(
requirements
.
isNetworkRequired
())
{
...
@@ -80,13 +98,14 @@ public final class WorkManagerScheduler implements Scheduler {
...
@@ -80,13 +98,14 @@ public final class WorkManagerScheduler implements Scheduler {
}
else
{
}
else
{
builder
.
setRequiredNetworkType
(
NetworkType
.
NOT_REQUIRED
);
builder
.
setRequiredNetworkType
(
NetworkType
.
NOT_REQUIRED
);
}
}
if
(
Util
.
SDK_INT
>=
23
&&
requirements
.
isIdleRequired
())
{
setRequiresDeviceIdle
(
builder
);
}
if
(
requirements
.
isChargingRequired
())
{
if
(
requirements
.
isChargingRequired
())
{
builder
.
setRequiresCharging
(
true
);
builder
.
setRequiresCharging
(
true
);
}
}
if
(
requirements
.
isStorageNotLowRequired
())
{
if
(
requirements
.
isIdleRequired
()
&&
Util
.
SDK_INT
>=
23
)
{
builder
.
setRequiresStorageNotLow
(
true
);
setRequiresDeviceIdle
(
builder
);
}
}
return
builder
.
build
();
return
builder
.
build
();
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java
View file @
496a315d
...
@@ -658,6 +658,22 @@ public abstract class DownloadService extends Service {
...
@@ -658,6 +658,22 @@ public abstract class DownloadService extends Service {
if
(
requirements
==
null
)
{
if
(
requirements
==
null
)
{
Log
.
e
(
TAG
,
"Ignored SET_REQUIREMENTS: Missing "
+
KEY_REQUIREMENTS
+
" extra"
);
Log
.
e
(
TAG
,
"Ignored SET_REQUIREMENTS: Missing "
+
KEY_REQUIREMENTS
+
" extra"
);
}
else
{
}
else
{
@Nullable
Scheduler
scheduler
=
getScheduler
();
if
(
scheduler
!=
null
)
{
Requirements
supportedRequirements
=
scheduler
.
getSupportedRequirements
(
requirements
);
if
(!
supportedRequirements
.
equals
(
requirements
))
{
Log
.
w
(
TAG
,
"Ignoring requirements not supported by the Scheduler: "
+
(
requirements
.
getRequirements
()
^
supportedRequirements
.
getRequirements
()));
// We need to make sure DownloadManager only uses requirements supported by the
// Scheduler. If we don't do this, DownloadManager can report itself as idle due to an
// unmet requirement that the Scheduler doesn't support. This can then lead to the
// service being destroyed, even though the Scheduler won't be able to restart it when
// the requirement is subsequently met.
requirements
=
supportedRequirements
;
}
}
downloadManager
.
setRequirements
(
requirements
);
downloadManager
.
setRequirements
(
requirements
);
}
}
break
;
break
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/scheduler/PlatformScheduler.java
View file @
496a315d
...
@@ -50,6 +50,12 @@ public final class PlatformScheduler implements Scheduler {
...
@@ -50,6 +50,12 @@ public final class PlatformScheduler implements Scheduler {
private
static
final
String
KEY_SERVICE_ACTION
=
"service_action"
;
private
static
final
String
KEY_SERVICE_ACTION
=
"service_action"
;
private
static
final
String
KEY_SERVICE_PACKAGE
=
"service_package"
;
private
static
final
String
KEY_SERVICE_PACKAGE
=
"service_package"
;
private
static
final
String
KEY_REQUIREMENTS
=
"requirements"
;
private
static
final
String
KEY_REQUIREMENTS
=
"requirements"
;
private
static
final
int
SUPPORTED_REQUIREMENTS
=
Requirements
.
NETWORK
|
Requirements
.
NETWORK_UNMETERED
|
Requirements
.
DEVICE_IDLE
|
Requirements
.
DEVICE_CHARGING
|
(
Util
.
SDK_INT
>=
26
?
Requirements
.
DEVICE_STORAGE_NOT_LOW
:
0
);
private
final
int
jobId
;
private
final
int
jobId
;
private
final
ComponentName
jobServiceComponentName
;
private
final
ComponentName
jobServiceComponentName
;
...
@@ -86,6 +92,11 @@ public final class PlatformScheduler implements Scheduler {
...
@@ -86,6 +92,11 @@ public final class PlatformScheduler implements Scheduler {
return
true
;
return
true
;
}
}
@Override
public
Requirements
getSupportedRequirements
(
Requirements
requirements
)
{
return
requirements
.
filterRequirements
(
SUPPORTED_REQUIREMENTS
);
}
// @RequiresPermission constructor annotation should ensure the permission is present.
// @RequiresPermission constructor annotation should ensure the permission is present.
@SuppressWarnings
(
"MissingPermission"
)
@SuppressWarnings
(
"MissingPermission"
)
private
static
JobInfo
buildJobInfo
(
private
static
JobInfo
buildJobInfo
(
...
@@ -94,8 +105,15 @@ public final class PlatformScheduler implements Scheduler {
...
@@ -94,8 +105,15 @@ public final class PlatformScheduler implements Scheduler {
Requirements
requirements
,
Requirements
requirements
,
String
serviceAction
,
String
serviceAction
,
String
servicePackage
)
{
String
servicePackage
)
{
JobInfo
.
Builder
builder
=
new
JobInfo
.
Builder
(
jobId
,
jobServiceComponentName
);
Requirements
filteredRequirements
=
requirements
.
filterRequirements
(
SUPPORTED_REQUIREMENTS
);
if
(!
filteredRequirements
.
equals
(
requirements
))
{
Log
.
w
(
TAG
,
"Ignoring unsupported requirements: "
+
(
filteredRequirements
.
getRequirements
()
^
requirements
.
getRequirements
()));
}
JobInfo
.
Builder
builder
=
new
JobInfo
.
Builder
(
jobId
,
jobServiceComponentName
);
if
(
requirements
.
isUnmeteredNetworkRequired
())
{
if
(
requirements
.
isUnmeteredNetworkRequired
())
{
builder
.
setRequiredNetworkType
(
JobInfo
.
NETWORK_TYPE_UNMETERED
);
builder
.
setRequiredNetworkType
(
JobInfo
.
NETWORK_TYPE_UNMETERED
);
}
else
if
(
requirements
.
isNetworkRequired
())
{
}
else
if
(
requirements
.
isNetworkRequired
())
{
...
@@ -103,6 +121,9 @@ public final class PlatformScheduler implements Scheduler {
...
@@ -103,6 +121,9 @@ public final class PlatformScheduler implements Scheduler {
}
}
builder
.
setRequiresDeviceIdle
(
requirements
.
isIdleRequired
());
builder
.
setRequiresDeviceIdle
(
requirements
.
isIdleRequired
());
builder
.
setRequiresCharging
(
requirements
.
isChargingRequired
());
builder
.
setRequiresCharging
(
requirements
.
isChargingRequired
());
if
(
Util
.
SDK_INT
>=
26
&&
requirements
.
isStorageNotLowRequired
())
{
builder
.
setRequiresStorageNotLow
(
true
);
}
builder
.
setPersisted
(
true
);
builder
.
setPersisted
(
true
);
PersistableBundle
extras
=
new
PersistableBundle
();
PersistableBundle
extras
=
new
PersistableBundle
();
...
...
library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java
View file @
496a315d
...
@@ -39,13 +39,13 @@ public final class Requirements implements Parcelable {
...
@@ -39,13 +39,13 @@ public final class Requirements implements Parcelable {
/**
/**
* Requirement flags. Possible flag values are {@link #NETWORK}, {@link #NETWORK_UNMETERED},
* Requirement flags. Possible flag values are {@link #NETWORK}, {@link #NETWORK_UNMETERED},
* {@link #DEVICE_IDLE}
and {@link #DEVICE_CHARGING
}.
* {@link #DEVICE_IDLE}
, {@link #DEVICE_CHARGING} and {@link #DEVICE_STORAGE_NOT_LOW
}.
*/
*/
@Documented
@Documented
@Retention
(
RetentionPolicy
.
SOURCE
)
@Retention
(
RetentionPolicy
.
SOURCE
)
@IntDef
(
@IntDef
(
flag
=
true
,
flag
=
true
,
value
=
{
NETWORK
,
NETWORK_UNMETERED
,
DEVICE_IDLE
,
DEVICE_CHARGING
})
value
=
{
NETWORK
,
NETWORK_UNMETERED
,
DEVICE_IDLE
,
DEVICE_CHARGING
,
DEVICE_STORAGE_NOT_LOW
})
public
@interface
RequirementFlags
{}
public
@interface
RequirementFlags
{}
/** Requirement that the device has network connectivity. */
/** Requirement that the device has network connectivity. */
...
@@ -56,6 +56,11 @@ public final class Requirements implements Parcelable {
...
@@ -56,6 +56,11 @@ public final class Requirements implements Parcelable {
public
static
final
int
DEVICE_IDLE
=
1
<<
2
;
public
static
final
int
DEVICE_IDLE
=
1
<<
2
;
/** Requirement that the device is charging. */
/** Requirement that the device is charging. */
public
static
final
int
DEVICE_CHARGING
=
1
<<
3
;
public
static
final
int
DEVICE_CHARGING
=
1
<<
3
;
/**
* Requirement that the device's <em>internal</em> storage is not low. Note that this requirement
* is not affected by the status of external storage.
*/
public
static
final
int
DEVICE_STORAGE_NOT_LOW
=
1
<<
4
;
@RequirementFlags
private
final
int
requirements
;
@RequirementFlags
private
final
int
requirements
;
...
@@ -74,6 +79,18 @@ public final class Requirements implements Parcelable {
...
@@ -74,6 +79,18 @@ public final class Requirements implements Parcelable {
return
requirements
;
return
requirements
;
}
}
/**
* Filters the requirements, returning the subset that are enabled by the provided filter.
*
* @param requirementsFilter The enabled {@link RequirementFlags}.
* @return The filtered requirements. If the filter does not cause a change in the requirements
* then this instance will be returned.
*/
public
Requirements
filterRequirements
(
int
requirementsFilter
)
{
int
filteredRequirements
=
requirements
&
requirementsFilter
;
return
filteredRequirements
==
requirements
?
this
:
new
Requirements
(
filteredRequirements
);
}
/** Returns whether network connectivity is required. */
/** Returns whether network connectivity is required. */
public
boolean
isNetworkRequired
()
{
public
boolean
isNetworkRequired
()
{
return
(
requirements
&
NETWORK
)
!=
0
;
return
(
requirements
&
NETWORK
)
!=
0
;
...
@@ -94,6 +111,11 @@ public final class Requirements implements Parcelable {
...
@@ -94,6 +111,11 @@ public final class Requirements implements Parcelable {
return
(
requirements
&
DEVICE_IDLE
)
!=
0
;
return
(
requirements
&
DEVICE_IDLE
)
!=
0
;
}
}
/** Returns whether the device is required to not be low on <em>internal</em> storage. */
public
boolean
isStorageNotLowRequired
()
{
return
(
requirements
&
DEVICE_STORAGE_NOT_LOW
)
!=
0
;
}
/**
/**
* Returns whether the requirements are met.
* Returns whether the requirements are met.
*
*
...
@@ -119,6 +141,9 @@ public final class Requirements implements Parcelable {
...
@@ -119,6 +141,9 @@ public final class Requirements implements Parcelable {
if
(
isIdleRequired
()
&&
!
isDeviceIdle
(
context
))
{
if
(
isIdleRequired
()
&&
!
isDeviceIdle
(
context
))
{
notMetRequirements
|=
DEVICE_IDLE
;
notMetRequirements
|=
DEVICE_IDLE
;
}
}
if
(
isStorageNotLowRequired
()
&&
!
isStorageNotLow
(
context
))
{
notMetRequirements
|=
DEVICE_STORAGE_NOT_LOW
;
}
return
notMetRequirements
;
return
notMetRequirements
;
}
}
...
@@ -145,8 +170,10 @@ public final class Requirements implements Parcelable {
...
@@ -145,8 +170,10 @@ public final class Requirements implements Parcelable {
}
}
private
boolean
isDeviceCharging
(
Context
context
)
{
private
boolean
isDeviceCharging
(
Context
context
)
{
@Nullable
Intent
batteryStatus
=
Intent
batteryStatus
=
context
.
registerReceiver
(
null
,
new
IntentFilter
(
Intent
.
ACTION_BATTERY_CHANGED
));
context
.
registerReceiver
(
/* receiver= */
null
,
new
IntentFilter
(
Intent
.
ACTION_BATTERY_CHANGED
));
if
(
batteryStatus
==
null
)
{
if
(
batteryStatus
==
null
)
{
return
false
;
return
false
;
}
}
...
@@ -162,6 +189,12 @@ public final class Requirements implements Parcelable {
...
@@ -162,6 +189,12 @@ public final class Requirements implements Parcelable {
:
Util
.
SDK_INT
>=
20
?
!
powerManager
.
isInteractive
()
:
!
powerManager
.
isScreenOn
();
:
Util
.
SDK_INT
>=
20
?
!
powerManager
.
isInteractive
()
:
!
powerManager
.
isScreenOn
();
}
}
private
boolean
isStorageNotLow
(
Context
context
)
{
return
context
.
registerReceiver
(
/* receiver= */
null
,
new
IntentFilter
(
Intent
.
ACTION_DEVICE_STORAGE_LOW
))
==
null
;
}
private
static
boolean
isInternetConnectivityValidated
(
ConnectivityManager
connectivityManager
)
{
private
static
boolean
isInternetConnectivityValidated
(
ConnectivityManager
connectivityManager
)
{
// It's possible to query NetworkCapabilities from API level 23, but RequirementsWatcher only
// It's possible to query NetworkCapabilities from API level 23, but RequirementsWatcher only
// fires an event to update its Requirements when NetworkCapabilities change from API level 24.
// fires an event to update its Requirements when NetworkCapabilities change from API level 24.
...
...
library/core/src/main/java/com/google/android/exoplayer2/scheduler/RequirementsWatcher.java
View file @
496a315d
...
@@ -104,6 +104,10 @@ public final class RequirementsWatcher {
...
@@ -104,6 +104,10 @@ public final class RequirementsWatcher {
filter
.
addAction
(
Intent
.
ACTION_SCREEN_OFF
);
filter
.
addAction
(
Intent
.
ACTION_SCREEN_OFF
);
}
}
}
}
if
(
requirements
.
isStorageNotLowRequired
())
{
filter
.
addAction
(
Intent
.
ACTION_DEVICE_STORAGE_LOW
);
filter
.
addAction
(
Intent
.
ACTION_DEVICE_STORAGE_OK
);
}
receiver
=
new
DeviceStatusChangeReceiver
();
receiver
=
new
DeviceStatusChangeReceiver
();
context
.
registerReceiver
(
receiver
,
filter
,
null
,
handler
);
context
.
registerReceiver
(
receiver
,
filter
,
null
,
handler
);
return
notMetRequirements
;
return
notMetRequirements
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/scheduler/Scheduler.java
View file @
496a315d
...
@@ -45,4 +45,14 @@ public interface Scheduler {
...
@@ -45,4 +45,14 @@ public interface Scheduler {
* @return Whether cancellation was successful.
* @return Whether cancellation was successful.
*/
*/
boolean
cancel
();
boolean
cancel
();
/**
* Checks whether this {@link Scheduler} supports the provided {@link Requirements}. If all of the
* requirements are supported then the same {@link Requirements} instance is returned. If not then
* a new instance is returned containing the subset of the requirements that are supported.
*
* @param requirements The requirements to check.
* @return The supported requirements.
*/
Requirements
getSupportedRequirements
(
Requirements
requirements
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment