getCurrentEvents();
325 |
326 | /**
327 | * Get the event with highest priority.
328 | *
329 | * @return The highest priority event that has been occurred.
330 | */
331 | protected @DynamicEvent String getHighestPriorityEvent();
332 |
333 | ...
334 | }
335 | ```
336 |
337 | ### Accessibility
338 |
339 | It has support for [accessibility service][accessibility service] to provide a better experience.
340 | It also provides more accurate results when detecting the [foreground app][foreground app].
341 |
342 | ### Dependency
343 |
344 | It depends on the [dynamic-utils][dynamic-utils] to perform
345 | various internal operations. So, its functions can also be used to perform other useful operations.
346 |
347 | ---
348 |
349 |
350 |
351 | ## Built with Dynamic
352 |
353 | Please email me if you are using this library and want to feature your app here. Also, please
354 | checkout the `Rotation` app to experience the full potential of this library.
355 |
356 | - [Rotation][rotation]
357 |
358 | ---
359 |
360 | ## Author
361 |
362 | Pranav Pandey
363 |
364 | [](https://github.com/pranavpandey)
365 | [](https://twitter.com/intent/follow?screen_name=pranavpandeydev)
366 | [](https://paypal.me/pranavpandeydev)
367 |
368 | ---
369 |
370 | ## License
371 |
372 | Copyright 2017-2024 Pranav Pandey
373 |
374 | Licensed under the Apache License, Version 2.0 (the "License");
375 | you may not use this file except in compliance with the License.
376 | You may obtain a copy of the License at
377 |
378 | http://www.apache.org/licenses/LICENSE-2.0
379 |
380 | Unless required by applicable law or agreed to in writing, software
381 | distributed under the License is distributed on an "AS IS" BASIS,
382 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
383 | See the License for the specific language governing permissions and
384 | limitations under the License.
385 |
386 |
387 | [android-support]: https://developer.android.com/topic/libraries/support-library/revisions.html#26-0-0
388 | [androidx]: https://developer.android.com/jetpack/androidx
389 | [androidx-migrate]: https://developer.android.com/jetpack/androidx/migrate
390 | [documentation]: https://pranavpandey.github.io/dynamic-engine
391 | [runtime permissions]: https://developer.android.com/training/permissions/requesting.html
392 | [usage stats manager]: https://developer.android.com/reference/android/app/usage/UsageStatsManager.html
393 | [accessibility service]: https://developer.android.com/guide/topics/ui/accessibility/service
394 | [foreground app]: https://github.com/pranavpandey/dynamic-engine/blob/942aa452076c154a6fc3b9698d80b1164093958e/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/DynamicEngine.java#L623
395 | [dynamic-utils]: https://github.com/pranavpandey/dynamic-utils
396 | [rotation]: https://play.google.com/store/apps/details?id=com.pranavpandey.rotation
397 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2025 Pranav Pandey
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 | buildscript {
18 | ext.versions = [
19 | 'compileSdk': 35,
20 | 'minSdk' : 21,
21 | 'targetSdk' : 35,
22 | 'buildTools': '35.0.0',
23 | 'dynamic' : '4.6.1',
24 | 'kotlin' : '1.9.24',
25 | 'preference': '1.2.1'
26 | ]
27 |
28 | repositories {
29 | mavenCentral()
30 | google()
31 | }
32 |
33 | dependencies {
34 | classpath 'com.android.tools.build:gradle:8.7.3'
35 | }
36 | }
37 |
38 | plugins {
39 | id("io.github.gradle-nexus.publish-plugin") version "2.0.0"
40 | }
41 |
42 | allprojects {
43 | repositories {
44 | mavenCentral()
45 | google()
46 | }
47 | }
48 |
49 | tasks.register('clean', Delete) {
50 | delete rootProject.layout.buildDirectory
51 | }
52 |
53 | ext {
54 | projectName = 'dynamic-engine'
55 | projectDesc = 'A collection of tasks to monitor various events including call, lock, ' +
56 | 'headset, charging, dock and foreground app via service on Android.'
57 | versionDesc = 'A collection of tasks to monitor various events including call, lock, ' +
58 | 'headset, charging, dock and foreground app via service on Android 4.0 (API 14) ' +
59 | 'and above.'
60 | referenceTitle = 'Dynamic Engine API reference'
61 |
62 | siteUrl = 'https://github.com/pranavpandey/dynamic-engine'
63 | gitUrl = 'https://github.com/pranavpandey/dynamic-engine'
64 | issueUrl = 'https://github.com/pranavpandey/dynamic-engine/issues'
65 | githubUrl = 'pranavpandey/dynamic-engine'
66 |
67 | mavenRepo = 'android'
68 | mavenGroup = 'com.pranavpandey.android'
69 | mavenDir = 'com/pranavpandey/android'
70 | mavenArtifactId = 'dynamic-engine'
71 | mavenInceptionYear = '2017'
72 | mavenVersion = '4.5.0'
73 | mavenVersionCode = 35
74 |
75 | developerId = 'pranavpandey'
76 | developerName = 'Pranav Pandey'
77 | developerEmail = 'dynamic@pranavpandey.com'
78 |
79 | licenseName = 'The Apache Software License, Version 2.0'
80 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
81 | licenseDistribution = 'repo'
82 | allLicenses = ["Apache-2.0"]
83 |
84 | publication = 'local.properties'
85 |
86 | ext["signing.keyId"] = ''
87 | ext["signing.password"] = ''
88 | ext["signing.secretKeyRingFile"] = ''
89 |
90 | ossrhUsername = ''
91 | ossrhPassword = ''
92 | sonatypeStagingProfileId = ''
93 | }
94 |
95 | apply plugin: 'io.github.gradle-nexus.publish-plugin'
96 |
97 | File publish = project.rootProject.file("${publication}")
98 | if (publish.exists()) {
99 | Properties properties = new Properties()
100 | new FileInputStream(publish).withCloseable { is -> properties.load(is) }
101 | properties.each { name, value -> ext[name] = value }
102 | }
103 |
104 | nexusPublishing {
105 | repositories {
106 | sonatype {
107 | username = ossrhUsername
108 | password = ossrhPassword
109 | stagingProfileId = sonatypeStagingProfileId
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/dynamic-engine/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2024 Pranav Pandey
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 | apply plugin: 'com.android.library'
18 |
19 | android {
20 | compileSdkVersion versions.compileSdk
21 | buildToolsVersion versions.buildTools
22 | namespace 'com.pranavpandey.android.dynamic.engine'
23 |
24 | defaultConfig {
25 | minSdkVersion versions.minSdk
26 | targetSdkVersion versions.targetSdk
27 | }
28 |
29 | sourceSets {
30 | main.res.srcDirs 'res'
31 | }
32 |
33 | compileOptions {
34 | sourceCompatibility JavaVersion.VERSION_17
35 | targetCompatibility JavaVersion.VERSION_17
36 | }
37 | }
38 |
39 | dependencies {
40 | implementation(platform("org.jetbrains.kotlin:kotlin-bom:${versions.kotlin}"))
41 |
42 | api "com.pranavpandey.android:dynamic-utils:${versions.dynamic}"
43 | api "androidx.preference:preference:${versions.preference}"
44 | }
45 |
46 | if (project.rootProject.file("${publication}").exists()) {
47 | apply from: 'maven.gradle'
48 | }
49 |
50 | tasks.register('generateJavadoc') {
51 | description "Generates Javadoc."
52 | }
53 |
54 | project.afterEvaluate {
55 | android.libraryVariants.configureEach { variant ->
56 | def task = project.tasks.create(
57 | "generate${variant.name.capitalize()}Javadoc", Javadoc) {
58 | title "${referenceTitle}${versionDesc}
${mavenVersion}
"
59 | description "Generates Javadoc for $variant.name."
60 | destinationDir = new File(destinationDir, variant.baseName)
61 |
62 | source = variant.sourceSets.collect {
63 | it.java.sourceFiles
64 | }.inject {
65 | m, i -> m + i
66 | }
67 | doFirst {
68 | classpath = project.files(variant.javaCompileProvider.get().classpath.files,
69 | project.android.getBootClasspath())
70 | }
71 |
72 | if (JavaVersion.current().isJava8Compatible()) {
73 | options.addStringOption('Xdoclint:none', '-quiet')
74 | }
75 |
76 | options.memberLevel = JavadocMemberLevel.PROTECTED
77 | exclude "**/R", "**/R.**", "**/R\$**", "**/BuildConfig*"
78 |
79 | options.windowTitle = "${referenceTitle}"
80 | options.links('http://docs.oracle.com/javase/8/docs/api',
81 | 'http://docs.oracle.com/javase/17/docs/api')
82 | options.links('https://developer.android.com/reference')
83 | options.linksOffline('https://developer.android.com/reference',
84 | 'https://developer.android.com/reference/androidx')
85 | options.links('https://pranavpandey.org/dynamic-utils')
86 |
87 | failOnError false
88 | }
89 |
90 | task.dependsOn "assemble${variant.name.capitalize()}"
91 | generateJavadoc.dependsOn task
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/dynamic-engine/maven.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2024 Pranav Pandey
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 | apply plugin: 'maven-publish'
18 | apply plugin: 'signing'
19 |
20 | group = mavenGroup
21 | version = mavenVersion
22 |
23 | // Android libraries
24 | if (project.hasProperty("android")) {
25 | tasks.register('sourcesJar', Jar) {
26 | archiveClassifier.set("sources")
27 | from android.sourceSets.main.java.srcDirs
28 | }
29 |
30 | tasks.register('javadoc', Javadoc) {
31 | dependsOn "generateReleaseRFile"
32 | title "${referenceTitle}${versionDesc}
${mavenVersion}
"
33 | failOnError = false
34 |
35 | source = android.sourceSets.main.java.sourceFiles
36 | doNotTrackState("Javadoc needs to be generated every time.")
37 |
38 | if (JavaVersion.current().isJava8Compatible()) {
39 | options.addStringOption('Xdoclint:none', '-quiet')
40 | }
41 |
42 | options.memberLevel = JavadocMemberLevel.PROTECTED
43 | exclude "**/R", "**/R.**", "**/R\$**", "**/BuildConfig*"
44 |
45 | options.windowTitle = "${referenceTitle}"
46 | options.links('http://docs.oracle.com/javase/8/docs/api',
47 | 'http://docs.oracle.com/javase/17/docs/api')
48 | options.links('https://developer.android.com/reference')
49 | options.linksOffline('https://developer.android.com/reference',
50 | 'https://developer.android.com/reference/androidx')
51 | options.links('https://pranavpandey.org/dynamic-utils')
52 | }
53 | } else { // Java libraries
54 | tasks.register('sourcesJar', Jar) {
55 | dependsOn classes
56 |
57 | archiveClassifier.set("sources")
58 | from sourceSets.main.allSource
59 | }
60 | }
61 |
62 | tasks.register('javadocJar', Jar) {
63 | dependsOn javadoc
64 |
65 | archiveClassifier.set("javadoc")
66 | from javadoc.destinationDir
67 | }
68 |
69 | artifacts {
70 | archives javadocJar
71 | archives sourcesJar
72 | }
73 |
74 | // Maven
75 | publishing {
76 | publications {
77 | library(MavenPublication) {
78 | groupId mavenGroup
79 | artifactId mavenArtifactId
80 | version mavenVersion
81 |
82 | artifact "$buildDir/outputs/aar/$mavenArtifactId-release.aar"
83 | artifact javadocJar
84 | artifact sourcesJar
85 |
86 | pom.withXml {
87 | // Project
88 | asNode().appendNode('name', projectName)
89 | asNode().appendNode('description', projectDesc)
90 | asNode().appendNode('url', siteUrl)
91 | asNode().appendNode('inceptionYear', mavenInceptionYear)
92 |
93 | // Licenses
94 | def license = asNode().appendNode('licenses').appendNode('license')
95 | license.appendNode('name', licenseName)
96 | license.appendNode('url', licenseUrl)
97 | license.appendNode('distribution', licenseDistribution)
98 |
99 | // Developers
100 | def developer = asNode().appendNode('developers').appendNode('developer')
101 | developer.appendNode('id', developerId)
102 | developer.appendNode('name', developerName)
103 | developer.appendNode('email', developerEmail)
104 |
105 | // SCM
106 | def scm = asNode().appendNode('scm')
107 | scm.appendNode('connection', "scm:git:${gitUrl}.git")
108 | scm.appendNode('developerConnection', gitUrl)
109 | scm.appendNode('url', siteUrl)
110 |
111 | // Dependencies
112 | def dependenciesNode = asNode()['dependencies'][0]
113 | if (dependenciesNode == null) {
114 | dependenciesNode = asNode().appendNode('dependencies')
115 | }
116 |
117 | // Add all that are 'compile' dependencies.
118 | configurations.api.allDependencies.each {
119 | def dependencyNode = dependenciesNode.appendNode('dependency')
120 | dependencyNode.appendNode('groupId', it.group)
121 | dependencyNode.appendNode('artifactId', it.name)
122 | dependencyNode.appendNode('version', it.version)
123 | }
124 | }
125 | }
126 | }
127 | }
128 |
129 | ext["signing.keyId"] = rootProject.ext["signing.keyId"]
130 | ext["signing.password"] = rootProject.ext["signing.password"]
131 | ext["signing.secretKeyRingFile"] = rootProject.ext["signing.secretKeyRingFile"]
132 |
133 | signing {
134 | sign publishing.publications
135 | }
136 |
137 | afterEvaluate { project ->
138 | // Fix javadoc generation.
139 | javadoc.classpath += files(android.libraryVariants.collect { variant ->
140 | variant.javaCompileProvider.get().classpath.files
141 | })
142 |
143 | def pomTask = "generatePomFileForLibraryPublication"
144 | def dependencies = [javadocJar, sourcesJar, assembleRelease, pomTask]
145 |
146 | // Convenience task to prepare everything we need for releases.
147 | tasks.register('prepareArtifacts') {
148 | dependsOn dependencies
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
21 |
22 |
23 |
25 |
26 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/DynamicEngine.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2024 Pranav Pandey
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.pranavpandey.android.dynamic.engine;
18 |
19 | import android.annotation.TargetApi;
20 | import android.app.KeyguardManager;
21 | import android.content.BroadcastReceiver;
22 | import android.content.Context;
23 | import android.content.Intent;
24 | import android.content.IntentFilter;
25 | import android.hardware.Sensor;
26 | import android.hardware.SensorEvent;
27 | import android.hardware.SensorEventListener;
28 | import android.hardware.SensorManager;
29 | import android.os.BatteryManager;
30 | import android.os.Build;
31 | import android.view.accessibility.AccessibilityEvent;
32 |
33 | import androidx.annotation.CallSuper;
34 | import androidx.annotation.NonNull;
35 | import androidx.annotation.Nullable;
36 | import androidx.core.content.ContextCompat;
37 |
38 | import com.pranavpandey.android.dynamic.engine.listener.DynamicEventListener;
39 | import com.pranavpandey.android.dynamic.engine.model.DynamicAppInfo;
40 | import com.pranavpandey.android.dynamic.engine.model.DynamicEvent;
41 | import com.pranavpandey.android.dynamic.engine.model.DynamicHinge;
42 | import com.pranavpandey.android.dynamic.engine.model.DynamicPriority;
43 | import com.pranavpandey.android.dynamic.engine.service.DynamicStickyService;
44 | import com.pranavpandey.android.dynamic.engine.task.DynamicAppMonitor;
45 | import com.pranavpandey.android.dynamic.engine.util.DynamicEngineUtils;
46 | import com.pranavpandey.android.dynamic.util.DynamicDeviceUtils;
47 | import com.pranavpandey.android.dynamic.util.DynamicSdkUtils;
48 | import com.pranavpandey.android.dynamic.util.DynamicTaskUtils;
49 |
50 | import java.util.ArrayList;
51 | import java.util.LinkedHashMap;
52 | import java.util.List;
53 | import java.util.Map;
54 |
55 | /**
56 | * Service to monitor various system events to provide event specific functionality in the app.
57 | * Extend this service and implement the interface methods to monitor the different events.
58 | *
59 | *
Package must be granted {@link android.Manifest.permission_group#PHONE}
60 | * permission to listen call events on API 23 and above.
61 | *
62 | *
Package must be granted {@link android.Manifest.permission#PACKAGE_USAGE_STATS}
63 | * permission to detect the foreground app on API 21 and above.
64 | */
65 | @TargetApi(Build.VERSION_CODES.R)
66 | public abstract class DynamicEngine extends DynamicStickyService
67 | implements SensorEventListener, DynamicEventListener {
68 |
69 | /**
70 | * Intent extra for headset state.
71 | */
72 | private static final String ADE_EXTRA_HEADSET_STATE = "state";
73 |
74 | /**
75 | * Sensor manager to register listeners.
76 | */
77 | private SensorManager mSensorManager;
78 |
79 | /**
80 | * Keyguard manager to detect the lock screen state.
81 | */
82 | private KeyguardManager mKeyguardManager;
83 |
84 | /**
85 | * Task to monitor foreground app.
86 | */
87 | private DynamicAppMonitor mDynamicAppMonitor;
88 |
89 | /**
90 | * Broadcast receiver to receive special events.
91 | */
92 | private SpecialEventReceiver mSpecialEventReceiver;
93 |
94 | /**
95 | * The dynamic hinge state.
96 | */
97 | private @DynamicHinge int mHinge;
98 |
99 | /**
100 | * {@code true} if the device is on call. Either ringing or answered.
101 | */
102 | private boolean mCall;
103 |
104 | /**
105 | * {@code true} if the device screen is off.
106 | */
107 | private boolean mScreenOff;
108 |
109 | /**
110 | * {@code true} if the device is in the locked state or the lock screen is shown.
111 | */
112 | private boolean mLocked;
113 |
114 | /**
115 | * {@code true} if the device is connected to a headset or a audio output device.
116 | */
117 | private boolean mHeadset;
118 |
119 | /**
120 | * {@code true} if the device is charging or connected to a power source.
121 | */
122 | private boolean mCharging;
123 |
124 | /**
125 | * {@code true} if the device is docked.
126 | */
127 | private boolean mDocked;
128 |
129 | /**
130 | * Array list to store the events priority.
131 | */
132 | private List mEventsPriority;
133 |
134 | /**
135 | * Hash map to store the active events.
136 | */
137 | private Map mEventsMap;
138 |
139 | @Override
140 | public void onCreate() {
141 | super.onCreate();
142 |
143 | mSensorManager = ContextCompat.getSystemService(this, SensorManager.class);
144 | mKeyguardManager = ContextCompat.getSystemService(this, KeyguardManager.class);
145 | mDynamicAppMonitor = new DynamicAppMonitor(this);
146 | mSpecialEventReceiver = new SpecialEventReceiver();
147 |
148 | ContextCompat.registerReceiver(this, mSpecialEventReceiver,
149 | DynamicEngineUtils.getEventsIntentFilter(), ContextCompat.RECEIVER_EXPORTED);
150 | ContextCompat.registerReceiver(this, mSpecialEventReceiver,
151 | DynamicEngineUtils.getPackageIntentFilter(), ContextCompat.RECEIVER_EXPORTED);
152 | ContextCompat.registerReceiver(this, mSpecialEventReceiver,
153 | DynamicEngineUtils.getCallIntentFilter(), ContextCompat.RECEIVER_EXPORTED);
154 | updateEventsPriority();
155 |
156 | mEventsMap = new LinkedHashMap<>();
157 | updateEventsMap(DynamicEvent.NONE, true);
158 | }
159 |
160 | /**
161 | * Initialize special events and check for some already occurred and ongoing events.
162 | */
163 | public void initializeEvents() {
164 | if (DynamicDeviceUtils.hasHingeFeature(this)) {
165 | mSensorManager.registerListener(this,
166 | mSensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE),
167 | SensorManager.SENSOR_DELAY_NORMAL);
168 | } else {
169 | setHinge(DynamicHinge.UNKNOWN);
170 | }
171 |
172 | Intent chargingIntent = ContextCompat.registerReceiver(this, null,
173 | new IntentFilter(Intent.ACTION_BATTERY_CHANGED), ContextCompat.RECEIVER_EXPORTED);
174 | if (chargingIntent != null) {
175 | int status = chargingIntent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
176 | mCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
177 | status == BatteryManager.BATTERY_STATUS_FULL;
178 | }
179 |
180 | Intent headsetIntent = ContextCompat.registerReceiver(this, null,
181 | new IntentFilter(Intent.ACTION_HEADSET_PLUG), ContextCompat.RECEIVER_EXPORTED);
182 | if (headsetIntent != null) {
183 | mHeadset = headsetIntent.getIntExtra(ADE_EXTRA_HEADSET_STATE, -1) == 1;
184 | }
185 |
186 | Intent dockIntent = ContextCompat.registerReceiver(this, null,
187 | new IntentFilter(Intent.ACTION_DOCK_EVENT), ContextCompat.RECEIVER_EXPORTED);
188 | if (dockIntent != null) {
189 | mDocked = dockIntent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1)
190 | != Intent.EXTRA_DOCK_STATE_UNDOCKED;
191 | }
192 |
193 | onInitialize(mCharging, mHeadset, mDocked);
194 | }
195 |
196 | /**
197 | * Update the events priority.
198 | */
199 | public void updateEventsPriority() {
200 | mEventsPriority = DynamicPriority.getEventsPriority(this);
201 | }
202 |
203 | /**
204 | * Update the status of an event.
205 | *
206 | * @param event The event to update the status.
207 | * @param active {@code true} to if the event is currently active.
208 | */
209 | public void updateEventsMap(@DynamicEvent @NonNull String event, boolean active) {
210 | mEventsMap.remove(event);
211 |
212 | if (active) {
213 | mEventsMap.put(event, event);
214 | }
215 | }
216 |
217 | /**
218 | * Get the sensor manager used by this service.
219 | *
220 | * @return The sensor manager used by this service.
221 | */
222 | public @NonNull SensorManager getSensorManager() {
223 | return mSensorManager;
224 | }
225 |
226 | /**
227 | * Get the listener to listen special events.
228 | *
229 | * @return The listener to listen special events.
230 | */
231 | public @NonNull DynamicEventListener getSpecialEventListener() {
232 | return this;
233 | }
234 |
235 | /**
236 | * Get the task to monitor foreground app.
237 | *
238 | * @return The task to monitor foreground app.
239 | */
240 | public @NonNull DynamicAppMonitor getAppMonitor() {
241 | return mDynamicAppMonitor;
242 | }
243 |
244 | /**
245 | * Enable or disable the foreground app monitor task.
246 | *
247 | * @param running {@code true} to start monitoring the foreground app and receive
248 | * listener callback.
249 | *
250 | * @see DynamicEventListener#onAppChange(DynamicAppInfo)
251 | */
252 | public void setAppMonitorTask(boolean running) {
253 | if (running) {
254 | if (mDynamicAppMonitor.isCancelled()) {
255 | mDynamicAppMonitor = new DynamicAppMonitor(this);
256 | }
257 |
258 | getAppMonitor().setRunning(true);
259 | DynamicTaskUtils.executeTask(getAppMonitor());
260 | } else {
261 | getAppMonitor().setRunning(false);
262 | DynamicTaskUtils.cancelTask(getAppMonitor(), true);
263 | }
264 |
265 | updateEventsMap(DynamicEvent.APP, getAppMonitor().isRunning());
266 | }
267 |
268 | /**
269 | * Pause or resume the foreground app monitor task.
270 | *
271 | * @param paused {@code true} to pause monitoring the foreground app and bo not receive
272 | * listener callback.
273 | *
274 | * @see DynamicEventListener#onAppChange(DynamicAppInfo)
275 | */
276 | public void setAppMonitorTaskPaused(boolean paused) {
277 | getAppMonitor().setPaused(paused);
278 | }
279 |
280 | @Override
281 | public void onDestroy() {
282 | try {
283 | unregisterReceiver(mSpecialEventReceiver);
284 | setAppMonitorTask(false);
285 |
286 | if (DynamicSdkUtils.is30()) {
287 | mSensorManager.unregisterListener(this);
288 | }
289 | } catch (Exception ignored) {
290 | }
291 | super.onDestroy();
292 | }
293 |
294 | /**
295 | * Returns the dynamic hinge state.
296 | *
297 | * @return The dynamic hinge state.
298 | */
299 | public @DynamicHinge int getHinge() {
300 | return mHinge;
301 | }
302 |
303 | /**
304 | * Set the dynamic hinge state.
305 | *
306 | * @param hinge The hinge state to be set.
307 | */
308 | public void setHinge(@DynamicHinge int hinge) {
309 | if (hinge != getHinge()) {
310 | this.mHinge = hinge;
311 |
312 | onHingeStateChange(hinge);
313 | }
314 | }
315 |
316 | /**
317 | * Get the status of call event.
318 | *
319 | * @return {@code true} if the device is on call.
320 | * Either ringing or answered.
321 | */
322 | public boolean isCall() {
323 | return mCall;
324 | }
325 |
326 | /**
327 | * Set the status of call event.
328 | *
329 | * @param call {@code true} if the device is on call.
330 | *
Either ringing or answered.
331 | */
332 | public void setCall(boolean call) {
333 | if (call != isCall()) {
334 | this.mCall = call;
335 |
336 | onCallStateChange(call);
337 | }
338 | }
339 |
340 | /**
341 | * Get the status of screen of event.
342 | *
343 | * @return {@code true} if the device screen is off.
344 | */
345 | public boolean isScreenOff() {
346 | return mScreenOff;
347 | }
348 |
349 | /**
350 | * Set the status of screen off event.
351 | *
352 | * @param screenOff {@code true} if the device screen is off.
353 | */
354 | public void setScreenOff(boolean screenOff) {
355 | if (screenOff != isScreenOff()) {
356 | this.mScreenOff = screenOff;
357 |
358 | onScreenStateChange(screenOff);
359 | }
360 | }
361 |
362 | /**
363 | * Get the status of lock event.
364 | *
365 | * @return {@code true} if the device is in the locked state or the lock screen is shown.
366 | */
367 | public boolean isLocked() {
368 | return mLocked;
369 | }
370 |
371 | /**
372 | * Set the status of lock event.
373 | *
374 | * @param locked {@code true} if the device is in the locked state or the lock screen
375 | * is shown.
376 | */
377 | public void setLocked(boolean locked) {
378 | if (locked != isLocked()) {
379 | this.mLocked = locked;
380 |
381 | onLockStateChange(locked);
382 | }
383 | }
384 |
385 | /**
386 | * Get the status of the headset event.
387 | *
388 | * @return {@code true} if the device is connected to a headset or a audio output device.
389 | */
390 | public boolean isHeadset() {
391 | return mHeadset;
392 | }
393 |
394 | /**
395 | * Set the status of headset event.
396 | *
397 | * @param headset {@code true} if the device is connected to a headset or a audio output
398 | * device.
399 | */
400 | public void setHeadset(boolean headset) {
401 | if (headset != isHeadset()) {
402 | this.mHeadset = headset;
403 |
404 | onHeadsetStateChange(headset);
405 | }
406 | }
407 |
408 | /**
409 | * Get the status of charging event.
410 | *
411 | * @return {@code true} if the device is charging or connected to a power source.
412 | */
413 | public boolean isCharging() {
414 | return mCharging;
415 | }
416 |
417 | /**
418 | * Set the status of charging event.
419 | *
420 | * @param charging {@code true} if the device is charging or connected to a power source.
421 | */
422 | public void setCharging(boolean charging) {
423 | if (charging != isCharging()) {
424 | this.mCharging = charging;
425 |
426 | onChargingStateChange(charging);
427 | }
428 | }
429 |
430 | /**
431 | * Get the status of dock event.
432 | *
433 | * @return {@code true} if the device is docked.
434 | */
435 | public boolean isDocked() {
436 | return mDocked;
437 | }
438 |
439 | /**
440 | * Set the status of dock event.
441 | *
442 | * @param docked {@code true} if the device is docked.
443 | */
444 | public void setDocked(boolean docked) {
445 | if (docked != isDocked()) {
446 | this.mDocked = docked;
447 |
448 | onDockStateChange(docked);
449 | }
450 | }
451 |
452 | @Override
453 | public void onSensorChanged(SensorEvent sensorEvent) {
454 | if (DynamicSdkUtils.is30()
455 | && sensorEvent.sensor.getType() == Sensor.TYPE_HINGE_ANGLE) {
456 | final float[] value = sensorEvent.values;
457 | if (value == null || value.length == 0) {
458 | return;
459 | }
460 |
461 | if ((value[0] >= 90 && value[0] < 150) ||
462 | (value[0] > 180 && value[0] <= 270)) {
463 | setHinge(DynamicHinge.HALF_EXPANDED);
464 | } else if ((value[0] >= 150 && value[0] <= 180)
465 | || (value[0] > 270 && value[0] < 360)) {
466 | setHinge(DynamicHinge.FLAT);
467 | } else {
468 | setHinge(DynamicHinge.COLLAPSED);
469 | }
470 | }
471 | }
472 |
473 | @Override
474 | public void onAccuracyChanged(Sensor sensor, int i) { }
475 |
476 | /**
477 | * Broadcast receiver to listen various events.
478 | *
479 | * @see Intent#ACTION_POWER_CONNECTED
480 | * @see Intent#ACTION_POWER_DISCONNECTED
481 | * @see Intent#ACTION_HEADSET_PLUG
482 | * @see Intent#ACTION_DOCK_EVENT
483 | * @see Intent#ACTION_SCREEN_OFF
484 | * @see Intent#ACTION_SCREEN_ON
485 | * @see Intent#ACTION_PACKAGE_ADDED
486 | * @see Intent#ACTION_PACKAGE_REMOVED
487 | * @see DynamicEngineUtils#ACTION_ON_CALL
488 | * @see DynamicEngineUtils#ACTION_CALL_IDLE
489 | */
490 | class SpecialEventReceiver extends BroadcastReceiver {
491 |
492 | private boolean isReplacing;
493 |
494 | @Override
495 | public void onReceive(@NonNull Context context, @Nullable Intent intent) {
496 | if (intent != null && intent.getAction() != null) {
497 | switch (intent.getAction()) {
498 | case Intent.ACTION_POWER_CONNECTED:
499 | setCharging(true);
500 | break;
501 | case Intent.ACTION_POWER_DISCONNECTED:
502 | setCharging(false);
503 | break;
504 | case Intent.ACTION_HEADSET_PLUG:
505 | setHeadset(intent.getIntExtra(ADE_EXTRA_HEADSET_STATE, 0) == 1);
506 | break;
507 | case Intent.ACTION_DOCK_EVENT:
508 | setDocked(intent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1)
509 | != Intent.EXTRA_DOCK_STATE_UNDOCKED);
510 | break;
511 | case Intent.ACTION_SCREEN_OFF:
512 | setScreenOff(true);
513 | setAppMonitorTaskPaused(true);
514 |
515 | if (mKeyguardManager != null) {
516 | setLocked(isKeyguardLocked());
517 | }
518 | break;
519 | case Intent.ACTION_SCREEN_ON:
520 | setScreenOff(false);
521 | setAppMonitorTaskPaused(false);
522 |
523 | if (mKeyguardManager != null) {
524 | setLocked(isKeyguardLocked());
525 | }
526 | break;
527 | case Intent.ACTION_USER_PRESENT:
528 | if (mKeyguardManager != null) {
529 | setLocked(isKeyguardLocked());
530 | }
531 | break;
532 | case Intent.ACTION_PACKAGE_REMOVED:
533 | if (intent.getData() != null
534 | && intent.getData().getSchemeSpecificPart() != null) {
535 | isReplacing = intent.getBooleanExtra(
536 | Intent.EXTRA_REPLACING, false);
537 |
538 | if (!isReplacing) {
539 | onPackageRemoved(intent.getData().getSchemeSpecificPart());
540 | }
541 | }
542 | break;
543 | case Intent.ACTION_PACKAGE_ADDED:
544 | if (intent.getData() != null
545 | && intent.getData().getSchemeSpecificPart() != null) {
546 | onPackageUpdated(DynamicEngineUtils.getAppInfoFromPackage(context,
547 | intent.getData().getSchemeSpecificPart()), !isReplacing);
548 | }
549 | break;
550 | case DynamicEngineUtils.ACTION_ON_CALL:
551 | setCall(true);
552 | break;
553 | case DynamicEngineUtils.ACTION_CALL_IDLE:
554 | setCall(false);
555 | break;
556 | }
557 | }
558 | }
559 |
560 | /**
561 | * Checks whether the keyguard is in the locked state.
562 | *
563 | * @return {@code true} if the keyguard is in the locked state.
564 | */
565 | @SuppressWarnings("deprecation")
566 | private boolean isKeyguardLocked() {
567 | if (mKeyguardManager == null) {
568 | return false;
569 | }
570 |
571 | return DynamicSdkUtils.is16() ? mKeyguardManager.isKeyguardLocked()
572 | : mKeyguardManager.inKeyguardRestrictedInputMode();
573 | }
574 | }
575 |
576 | @CallSuper
577 | @Override
578 | public void onInitialize(boolean charging, boolean headset, boolean docked) {
579 | updateEventsMap(DynamicEvent.CHARGING, charging);
580 | updateEventsMap(DynamicEvent.HEADSET, headset);
581 | updateEventsMap(DynamicEvent.DOCK, docked);
582 | }
583 |
584 | @CallSuper
585 | @Override
586 | public void onHingeStateChange(@DynamicHinge int state) { }
587 |
588 | @CallSuper
589 | @Override
590 | public void onCallStateChange(boolean call) {
591 | updateEventsMap(DynamicEvent.CALL, call);
592 | }
593 |
594 | @CallSuper
595 | @Override
596 | public void onScreenStateChange(boolean screenOff) { }
597 |
598 | @CallSuper
599 | @Override
600 | public void onLockStateChange(boolean locked) {
601 | updateEventsMap(DynamicEvent.LOCK, locked);
602 | }
603 |
604 | @CallSuper
605 | @Override
606 | public void onHeadsetStateChange(boolean connected) {
607 | updateEventsMap(DynamicEvent.HEADSET, connected);
608 | }
609 |
610 | @CallSuper
611 | @Override
612 | public void onChargingStateChange(boolean charging) {
613 | updateEventsMap(DynamicEvent.CHARGING, charging);
614 | }
615 |
616 | @CallSuper
617 | @Override
618 | public void onDockStateChange(boolean docked) {
619 | updateEventsMap(DynamicEvent.DOCK, docked);
620 | }
621 |
622 | @CallSuper
623 | @Override
624 | public void onAppChange(@Nullable DynamicAppInfo dynamicAppInfo) {
625 | updateEventsMap(DynamicEvent.APP, getAppMonitor().isRunning());
626 | }
627 |
628 | @Override
629 | protected void onAccessibilityStateChanged(boolean enabled) {
630 | super.onAccessibilityStateChanged(enabled);
631 |
632 | getAppMonitor().setDormant(enabled);
633 | }
634 |
635 | @Override
636 | public void onAccessibilityEvent(AccessibilityEvent event) {
637 | super.onAccessibilityEvent(event);
638 |
639 | getAppMonitor().setDormant(true);
640 | getAppMonitor().onAccessibilityEvent(event);
641 | }
642 |
643 | /**
644 | * Retrieve the current ongoing events.
645 | *
646 | * @return The list of current ongoing events.
647 | */
648 | protected @NonNull List getCurrentEvents() {
649 | if (mEventsPriority == null) {
650 | updateEventsPriority();
651 | }
652 |
653 | List currentEvents = new ArrayList<>();
654 | currentEvents.add(DynamicEvent.NONE);
655 |
656 | for (String eventPriority : mEventsPriority) {
657 | if (mEventsMap.containsKey(eventPriority)) {
658 | currentEvents.add(mEventsMap.get(eventPriority));
659 | }
660 | }
661 |
662 | return currentEvents;
663 | }
664 |
665 | /**
666 | * Get the event according to its priority.
667 | *
668 | * @param currentEvents The list of events.
669 | * @param priority The event priority to find event.
670 | *
671 | * @return The event according to its priority.
672 | */
673 | protected @DynamicEvent String getEventByPriority(@NonNull List currentEvents,
674 | int priority) {
675 | if (!currentEvents.isEmpty() && priority > 0 && priority <= currentEvents.size()) {
676 | return currentEvents.get(currentEvents.size() - priority);
677 | } else {
678 | return DynamicEvent.NONE;
679 | }
680 | }
681 |
682 | /**
683 | * Get the event with highest priority.
684 | *
685 | * @return The highest priority event that has been occurred.
686 | */
687 | protected @DynamicEvent String getHighestPriorityEvent() {
688 | return getEventByPriority(getCurrentEvents(), 1);
689 | }
690 | }
691 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/listener/DynamicEventListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2022 Pranav Pandey
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.pranavpandey.android.dynamic.engine.listener;
18 |
19 | import androidx.annotation.Nullable;
20 |
21 | import com.pranavpandey.android.dynamic.engine.DynamicEngine;
22 | import com.pranavpandey.android.dynamic.engine.model.DynamicAppInfo;
23 | import com.pranavpandey.android.dynamic.engine.model.DynamicHinge;
24 |
25 | /**
26 | * Interface to listen various system events with the help of {@link DynamicEngine}.
27 | */
28 | public interface DynamicEventListener {
29 |
30 | /**
31 | * This method will be called on initializing the service so that we can get the current
32 | * charging, headset and dock state.
33 | *
34 | * @param charging {@code true} if the device is charging or connected to a power source.
35 | * @param headset {@code true} if the device is connected to a headset or a audio output
36 | * device.
37 | * @param docked {@code true} if the device is docked.
38 | */
39 | void onInitialize(boolean charging, boolean headset, boolean docked);
40 |
41 | /**
42 | * This method will be called when the hinge state is changed.
43 | *
44 | * @param state The current hinge state.
45 | *
46 | * @see DynamicHinge
47 | */
48 | void onHingeStateChange(@DynamicHinge int state);
49 |
50 | /**
51 | * This method will be called when the call state is changed.
52 | * Either on call or the device is idle.
53 | *
54 | * @param call {@code true} if the device is on call.
55 | *
Either ringing or answered.
56 | */
57 | void onCallStateChange(boolean call);
58 |
59 | /**
60 | * This method will be called when the screen state is changed.
61 | *
Either the device screen is off or on.
62 | *
63 | * @param screenOff {@code true} if the device screen is off.
64 | */
65 | void onScreenStateChange(boolean screenOff);
66 |
67 | /**
68 | * This method will be called when the lock state is changed.
69 | *
Either the device is in the locked or unlocked state independent of the PIN,
70 | * password or any other security lock.
71 | *
72 | * @param locked {@code true} if the device is in the locked state or the lock screen is shown.
73 | */
74 | void onLockStateChange(boolean locked);
75 |
76 | /**
77 | * This method will be called when the headset state is changed.
78 | *
Either the device is connected to a audio output device or volume is routed through
79 | * the internal speaker.
80 | *
81 | * @param connected {@code true} if the device is connected to a headset or a audio output
82 | * device.
83 | */
84 | void onHeadsetStateChange(boolean connected);
85 |
86 | /**
87 | * This method will be called when the charging state is changed.
88 | *
Either the device is connected to a power source using the battery.
89 | *
90 | * @param charging {@code true} if the device is charging or connected to a power source.
91 | */
92 | void onChargingStateChange(boolean charging);
93 |
94 | /**
95 | * This method will be called when the dock state is changed.
96 | *
Either the device is docked or not.
97 | *
98 | * @param docked {@code true} if the device is docked.
99 | */
100 | void onDockStateChange(boolean docked);
101 |
102 | /**
103 | * This method will be called when the foreground app is changed.
104 | *
Use it to provide the app specific functionality in the app.
105 | *
106 | * @param dynamicAppInfo The dynamic app info of the foreground package.
107 | */
108 | void onAppChange(@Nullable DynamicAppInfo dynamicAppInfo);
109 |
110 | /**
111 | * This method will be called when an app package is added or changed.
112 | *
Useful to show a notification if an app is updated or a new app is installed.
113 | *
114 | * @param dynamicAppInfo The dynamic app info of the updated or added package.
115 | * @param newPackage {@code true} if the package is newly added.
116 | */
117 | void onPackageUpdated(@Nullable DynamicAppInfo dynamicAppInfo, boolean newPackage);
118 |
119 | /**
120 | * This method will be called when an app package is removed.
121 | *
Useful to show some work when a package is removed.
122 | *
123 | * @param packageName The package which was removed.
124 | */
125 | void onPackageRemoved(@Nullable String packageName);
126 | }
127 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/model/DynamicAppInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2022 Pranav Pandey
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.pranavpandey.android.dynamic.engine.model;
18 |
19 | import android.content.ComponentName;
20 | import android.content.pm.ApplicationInfo;
21 | import android.os.Parcel;
22 | import android.os.Parcelable;
23 |
24 | import androidx.annotation.NonNull;
25 | import androidx.annotation.Nullable;
26 |
27 | /**
28 | * Collection of various properties for a given package for an easy data interchange.
29 | */
30 | public class DynamicAppInfo implements Parcelable {
31 |
32 | /**
33 | * Application info.
34 | */
35 | private ApplicationInfo applicationInfo;
36 |
37 | /**
38 | * Package name.
39 | */
40 | private String packageName;
41 |
42 | /**
43 | * Top activity component name.
44 | */
45 | private ComponentName topActivity;
46 |
47 | /**
48 | * Application label or name.
49 | */
50 | private String label;
51 |
52 | /**
53 | * Default constructor to initialize the dynamic app info.
54 | */
55 | public DynamicAppInfo() { }
56 |
57 | /**
58 | * Read an object of this class from the parcel.
59 | *
60 | * @param in The parcel to read the values.
61 | */
62 | public DynamicAppInfo(@NonNull Parcel in) {
63 | this.applicationInfo = in.readParcelable(ApplicationInfo.class.getClassLoader());
64 | this.topActivity = in.readParcelable(ComponentName.class.getClassLoader());
65 | this.packageName = in.readString();
66 | this.label = in.readString();
67 | }
68 |
69 | /**
70 | * Parcelable creator to create from parcel.
71 | */
72 | public static final Parcelable.Creator CREATOR =
73 | new Parcelable.Creator() {
74 | @Override
75 | public DynamicAppInfo createFromParcel(Parcel in) {
76 | return new DynamicAppInfo(in);
77 | }
78 |
79 | @Override
80 | public DynamicAppInfo[] newArray(int size) {
81 | return new DynamicAppInfo[size];
82 | }
83 | };
84 |
85 | @Override
86 | public int describeContents() {
87 | return hashCode();
88 | }
89 |
90 | @Override
91 | public void writeToParcel(Parcel dest, int flags) {
92 | dest.writeParcelable(applicationInfo, flags);
93 | dest.writeParcelable(topActivity, flags);
94 | dest.writeString(packageName);
95 | dest.writeString(label);
96 | }
97 |
98 | /**
99 | * Get the application info.
100 | *
101 | * @return The application info.
102 | */
103 | public @Nullable ApplicationInfo getApplicationInfo() {
104 | return applicationInfo;
105 | }
106 |
107 | /**
108 | * Set the application info.
109 | *
110 | * @param applicationInfo The application info to be set.
111 | */
112 | public void setApplicationInfo(@Nullable ApplicationInfo applicationInfo) {
113 | this.applicationInfo = applicationInfo;
114 | }
115 |
116 | /**
117 | * Get the package name.
118 | *
119 | * @return The package name.
120 | */
121 | public @Nullable String getPackageName() {
122 | return packageName;
123 | }
124 |
125 | /**
126 | * Set the package name.
127 | *
128 | * @param packageName The package name to be set.
129 | */
130 | public void setPackageName(@Nullable String packageName) {
131 | this.packageName = packageName;
132 | }
133 |
134 | /**
135 | * Get the top activity component name.
136 | *
137 | * @return The top activity component name.
138 | */
139 | public @Nullable ComponentName getTopActivity() {
140 | return topActivity;
141 | }
142 |
143 | /**
144 | * Set the top activity component name.
145 | *
146 | * @param topActivity The top activity component name to be set.
147 | */
148 | public void setTopActivity(@Nullable ComponentName topActivity) {
149 | this.topActivity = topActivity;
150 | }
151 |
152 | /**
153 | * Get the application label or name.
154 | *
155 | * @return The application label or name.
156 | */
157 | public @Nullable String getLabel() {
158 | return label;
159 | }
160 |
161 | /**
162 | * Set the application label or name.
163 | *
164 | * @param label The application label or name to be set.
165 | */
166 | public void setLabel(@Nullable String label) {
167 | this.label = label;
168 | }
169 |
170 | /**
171 | * Compare the object of this class with another object.
172 | *
173 | * @param dynamicAppInfo The other DynamicAppInfo to compare.
174 | *
175 | * @return {@code true} if the two objects are equal.
176 | */
177 | public boolean equals(@NonNull DynamicAppInfo dynamicAppInfo) {
178 | return !(getPackageName() != null && dynamicAppInfo.getPackageName() != null)
179 | || getPackageName().equals(dynamicAppInfo.getPackageName());
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/model/DynamicEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2022 Pranav Pandey
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.pranavpandey.android.dynamic.engine.model;
18 |
19 | import com.pranavpandey.android.dynamic.engine.DynamicEngine;
20 |
21 | import java.lang.annotation.Retention;
22 | import java.lang.annotation.RetentionPolicy;
23 |
24 | /**
25 | * Events supported by the {@link DynamicEngine}.
26 | */
27 | @Retention(RetentionPolicy.SOURCE)
28 | public @interface DynamicEvent {
29 |
30 | /**
31 | * Constant for no event.
32 | */
33 | String NONE = "-1";
34 |
35 | /**
36 | * Constant for the call event.
37 | */
38 | String CALL = "0";
39 |
40 | /**
41 | * Constant for the lock event.
42 | */
43 | String LOCK = "1";
44 |
45 | /**
46 | * Constant for the headset event.
47 | */
48 | String HEADSET = "2";
49 |
50 | /**
51 | * Constant for the charging event.
52 | */
53 | String CHARGING = "3";
54 |
55 | /**
56 | * Constant for the dock event.
57 | */
58 | String DOCK = "4";
59 |
60 | /**
61 | * Constant for the app event.
62 | */
63 | String APP = "5";
64 | }
65 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/model/DynamicHinge.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2022 Pranav Pandey
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.pranavpandey.android.dynamic.engine.model;
18 |
19 | import com.pranavpandey.android.dynamic.engine.DynamicEngine;
20 |
21 | import java.lang.annotation.Retention;
22 | import java.lang.annotation.RetentionPolicy;
23 |
24 | /**
25 | * Hinge states supported by the {@link DynamicEngine}.
26 | */
27 | @Retention(RetentionPolicy.SOURCE)
28 | public @interface DynamicHinge {
29 |
30 | /**
31 | * Constant for the unknown hinge state.
32 | */
33 | int UNKNOWN = -1;
34 |
35 | /**
36 | * Constant for the collapsed hinge state.
37 | */
38 | int COLLAPSED = 0;
39 |
40 | /**
41 | * Constant for the half expanded hinge state.
42 | */
43 | int HALF_EXPANDED = 1;
44 |
45 | /**
46 | * Constant for the flat hinge state.
47 | */
48 | int FLAT = 2;
49 | }
50 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/model/DynamicPriority.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2022 Pranav Pandey
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.pranavpandey.android.dynamic.engine.model;
18 |
19 | import android.content.Context;
20 | import android.content.SharedPreferences;
21 |
22 | import androidx.annotation.NonNull;
23 | import androidx.preference.PreferenceManager;
24 |
25 | import com.pranavpandey.android.dynamic.util.DynamicDeviceUtils;
26 |
27 | import java.util.ArrayList;
28 | import java.util.Arrays;
29 | import java.util.Collections;
30 | import java.util.List;
31 |
32 | /**
33 | * Helper class to manage priority of the different events in case two or more events will
34 | * occur simultaneously.
35 | */
36 | public class DynamicPriority {
37 |
38 | /**
39 | * Shared preference key for the event priorities.
40 | */
41 | public static final String ADE_PREF_EVENTS_PRIORITY = "ade_pref_events_priority";
42 |
43 | /**
44 | * DynamicPriority splitter to separate different events.
45 | */
46 | public static final String ADE_PRIORITY_SPLIT = ",";
47 |
48 | /**
49 | * Default priority for the events.
50 | *
51 | * {@code 1.} Call (highest)
52 | *
{@code 2.} Lock
53 | *
{@code 3.} Headset
54 | *
{@code 4.} Charging
55 | *
{@code 5.} Dock
56 | *
{@code 6.} App (lowest)
57 | */
58 | private static final String ADE_DEFAULT_EVENTS_PRIORITY = DynamicEvent.DOCK
59 | + ADE_PRIORITY_SPLIT + DynamicEvent.CHARGING + ADE_PRIORITY_SPLIT
60 | + DynamicEvent.HEADSET + ADE_PRIORITY_SPLIT + DynamicEvent.LOCK
61 | + ADE_PRIORITY_SPLIT + DynamicEvent.CALL;
62 |
63 | /**
64 | * Get shared preferences of the app engine for a given context.
65 | *
66 | * @param context The context to get shared preferences.
67 | *
68 | * @return The shared preferences of the app engine for a given context.
69 | */
70 | private static @NonNull SharedPreferences getSharedPreferences(@NonNull Context context) {
71 | return PreferenceManager.getDefaultSharedPreferences(context);
72 | }
73 |
74 | /**
75 | * Reset the events priority to default.
76 | *
77 | * @param context The context to get shared preferences.
78 | *
79 | * @see #ADE_DEFAULT_EVENTS_PRIORITY
80 | */
81 | public static void resetPriority(@NonNull Context context) {
82 | getSharedPreferences(context).edit().putString(
83 | ADE_PREF_EVENTS_PRIORITY, ADE_DEFAULT_EVENTS_PRIORITY).apply();
84 | }
85 |
86 | /**
87 | * Save the events priority.
88 | *
89 | * @param context The context to get shared preferences.
90 | * @param eventsPriority The list containing events priority.
91 | */
92 | public static void saveEventsPriority(@NonNull Context context,
93 | @NonNull List eventsPriority) {
94 | Collections.reverse(eventsPriority);
95 | StringBuilder priorities = new StringBuilder();
96 | for (int i = 0; i < eventsPriority.size(); i++) {
97 | priorities.append(eventsPriority.get(i)).append(ADE_PRIORITY_SPLIT);
98 | }
99 |
100 | getSharedPreferences(context).edit().putString(
101 | ADE_PREF_EVENTS_PRIORITY, priorities.toString()).apply();
102 | }
103 |
104 | /**
105 | * Get the default events priority after checking the telephony functionality.
106 | *
107 | * @param context The context to get shared preferences.
108 | *
109 | * @return The default events priority.
110 | */
111 | public static List getDefaultEventsPriority(@NonNull Context context) {
112 | return returnAfterDeviceCheck(context, new ArrayList<>(
113 | convertStringToArrayList(ADE_DEFAULT_EVENTS_PRIORITY)));
114 | }
115 |
116 | /**
117 | * Get the saved events priority after checking the device for telephony and per app
118 | * functionality.
119 | *
120 | * @param context The context to get shared preferences.
121 | *
122 | * @return The saved events priority.
123 | */
124 | public static @NonNull List getEventsPriority(@NonNull Context context) {
125 | String eventsPriority = getSharedPreferences(context).getString(
126 | ADE_PREF_EVENTS_PRIORITY, ADE_DEFAULT_EVENTS_PRIORITY);
127 | if (eventsPriority == null) {
128 | eventsPriority = ADE_DEFAULT_EVENTS_PRIORITY;
129 | }
130 |
131 | return returnAfterDeviceCheck(context,
132 | new ArrayList<>(convertStringToArrayList(eventsPriority)));
133 | }
134 |
135 | /**
136 | * Get events priority after checking the device for telephony and per app functionality.
137 | *
138 | * @param context The context to get shared preferences.
139 | * @param eventsPriority The array list containing events priority.
140 | *
141 | * @return The events priority after device check.
142 | */
143 | private static @NonNull List returnAfterDeviceCheck(@NonNull Context context,
144 | @NonNull List eventsPriority) {
145 | if (!DynamicDeviceUtils.hasTelephony(context)) {
146 | eventsPriority.remove(DynamicEvent.CALL);
147 | }
148 |
149 | if (!eventsPriority.contains(DynamicEvent.APP)) {
150 | eventsPriority.add(0, DynamicEvent.APP);
151 | }
152 |
153 | return eventsPriority;
154 | }
155 |
156 | /**
157 | * Convert string to array list according to the priority splitter.
158 | * It will be used for the easy retrieval.
159 | *
160 | * @return The array list converted from the string.
161 | */
162 | private static @NonNull List convertStringToArrayList(@NonNull String string) {
163 | return new ArrayList<>(Arrays.asList(string.split(ADE_PRIORITY_SPLIT)));
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/receiver/DynamicStateReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2022 Pranav Pandey
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.pranavpandey.android.dynamic.engine.receiver;
18 |
19 | import android.content.BroadcastReceiver;
20 | import android.content.Context;
21 | import android.content.Intent;
22 | import android.telephony.TelephonyManager;
23 |
24 | import androidx.annotation.NonNull;
25 | import androidx.annotation.Nullable;
26 | import androidx.annotation.RestrictTo;
27 |
28 | import com.pranavpandey.android.dynamic.engine.util.DynamicEngineUtils;
29 |
30 | /**
31 | * Broadcast receiver to listen call events. It has been already added in the manifest and
32 | * should be registered dynamically at the runtime.
33 | *
34 | * Package must be granted {@link android.Manifest.permission_group#PHONE}
35 | * permission to listen call events on API 23 and above.
36 | */
37 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
38 | public class DynamicStateReceiver extends BroadcastReceiver {
39 |
40 | @Override
41 | public void onReceive(@NonNull Context context, @Nullable Intent intent) {
42 | if (intent == null || intent.getAction() == null) {
43 | return;
44 | }
45 |
46 | if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())) {
47 | String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
48 |
49 | if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)
50 | || TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) {
51 | context.sendBroadcast(new Intent(DynamicEngineUtils.ACTION_ON_CALL));
52 | } else {
53 | context.sendBroadcast(new Intent(DynamicEngineUtils.ACTION_CALL_IDLE));
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/service/DynamicStickyService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2024 Pranav Pandey
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.pranavpandey.android.dynamic.engine.service;
18 |
19 | import android.accessibilityservice.AccessibilityService;
20 | import android.accessibilityservice.AccessibilityServiceInfo;
21 | import android.annotation.TargetApi;
22 | import android.content.Intent;
23 | import android.os.Build;
24 | import android.view.accessibility.AccessibilityEvent;
25 | import android.view.accessibility.AccessibilityManager;
26 |
27 | import androidx.annotation.Nullable;
28 | import androidx.core.content.ContextCompat;
29 |
30 | import com.pranavpandey.android.dynamic.engine.task.DynamicAppMonitor;
31 | import com.pranavpandey.android.dynamic.util.DynamicSdkUtils;
32 |
33 | /**
34 | * Sticky service which will restart automatically if killed by the system.
35 | *
Useful in low memory or similar situations where we need to run the service continuously
36 | * in the background.
37 | */
38 | public abstract class DynamicStickyService extends AccessibilityService {
39 |
40 | /**
41 | * Default interval after which try to restart the service.
42 | */
43 | public static final long ADE_DEFAULT_RESTART_INTERVAL = 2000;
44 |
45 | @Override
46 | public void onCreate() {
47 | super.onCreate();
48 |
49 | try {
50 | AccessibilityManager am = ContextCompat.getSystemService(
51 | this, AccessibilityManager.class);
52 |
53 | if (am != null) {
54 | am.addAccessibilityStateChangeListener(
55 | new AccessibilityManager.AccessibilityStateChangeListener() {
56 | @Override
57 | public void onAccessibilityStateChanged(boolean enabled) {
58 | DynamicStickyService.this.onAccessibilityStateChanged(enabled);
59 | }
60 | });
61 | }
62 | } catch (Exception ignored) {
63 | }
64 | }
65 |
66 | @Override
67 | public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
68 | return START_STICKY;
69 | }
70 |
71 | /**
72 | * Get the restart interval after which try to restart the service.
73 | *
Override this method in the extended class to change the default interval.
74 | *
75 | * @return The interval in milliseconds after which service will be restarted.
76 | *
77 | * @see #ADE_DEFAULT_RESTART_INTERVAL
78 | */
79 | protected long getRestartInterval() {
80 | return ADE_DEFAULT_RESTART_INTERVAL;
81 | }
82 |
83 | @Override
84 | public void onAccessibilityEvent(AccessibilityEvent event) { }
85 |
86 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
87 | @Override
88 | protected void onServiceConnected() {
89 | super.onServiceConnected();
90 |
91 | if (DynamicSdkUtils.is16()) {
92 | AccessibilityServiceInfo info = getServiceInfo();
93 | if (info != null) {
94 | info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
95 | | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
96 | info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
97 | info.notificationTimeout = DynamicAppMonitor.ADE_NOTIFICATION_TIMEOUT;
98 | setServiceInfo(info);
99 | }
100 | }
101 | }
102 |
103 | @Override
104 | public void onInterrupt() { }
105 |
106 | /**
107 | * Called back on change in the accessibility state.
108 | *
109 | * @param enabled Whether accessibility is enabled.
110 | */
111 | protected void onAccessibilityStateChanged(boolean enabled) { }
112 | }
113 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/task/DynamicAppMonitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2024 Pranav Pandey
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.pranavpandey.android.dynamic.engine.task;
18 |
19 | import android.annotation.SuppressLint;
20 | import android.annotation.TargetApi;
21 | import android.app.ActivityManager;
22 | import android.app.usage.UsageStatsManager;
23 | import android.content.ComponentName;
24 | import android.content.Context;
25 | import android.os.Build;
26 | import android.view.accessibility.AccessibilityEvent;
27 |
28 | import androidx.annotation.NonNull;
29 | import androidx.annotation.Nullable;
30 | import androidx.annotation.RestrictTo;
31 | import androidx.core.content.ContextCompat;
32 |
33 | import com.pranavpandey.android.dynamic.engine.DynamicEngine;
34 | import com.pranavpandey.android.dynamic.engine.model.DynamicAppInfo;
35 | import com.pranavpandey.android.dynamic.engine.util.DynamicEngineUtils;
36 | import com.pranavpandey.android.dynamic.util.DynamicSdkUtils;
37 | import com.pranavpandey.android.dynamic.util.concurrent.DynamicResult;
38 | import com.pranavpandey.android.dynamic.util.concurrent.DynamicTask;
39 |
40 | import java.util.concurrent.atomic.AtomicBoolean;
41 |
42 | /**
43 | * A {@link DynamicTask} to monitor foreground to provide app specific functionality.
44 | *
45 | *
Package must be granted {@link android.Manifest.permission#PACKAGE_USAGE_STATS}
46 | * permission to detect the foreground app on API 21 and above.
47 | */
48 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
49 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
50 | public class DynamicAppMonitor extends DynamicTask {
51 |
52 | /**
53 | * Context constant for usage stats service.
54 | *
55 | * @see Context#USAGE_STATS_SERVICE
56 | */
57 | private static final String ADE_USAGE_STATS = "usagestats";
58 |
59 | /**
60 | * Default usage stats interval in milliseconds.
61 | */
62 | private static final long ADE_USAGE_STATS_INTERVAL = 2000L;
63 |
64 | /**
65 | * The minimal period in milliseconds between two events.
66 | */
67 | public static final long ADE_NOTIFICATION_TIMEOUT = 200L;
68 |
69 | /**
70 | * Dynamic engine to initialize usage stats service.
71 | */
72 | @SuppressLint("StaticFieldLeak")
73 | private DynamicEngine mDynamicEngine;
74 |
75 | /**
76 | * {@code true} if this task is running.
77 | */
78 | private final AtomicBoolean mRunning = new AtomicBoolean();
79 |
80 | /**
81 | * {@code true} if this task is paused.
82 | */
83 | private final AtomicBoolean mPaused = new AtomicBoolean();
84 |
85 | /**
86 | * {@code true} if this task is dormant and used only to notify events.
87 | */
88 | private final AtomicBoolean mDormant = new AtomicBoolean();
89 |
90 | /**
91 | * Dynamic app info for the foreground package.
92 | */
93 | private DynamicAppInfo mDynamicAppInfo;
94 |
95 | /**
96 | * Activity manager to detect foreground package activities.
97 | */
98 | private final ActivityManager mActivityManager;
99 |
100 | /**
101 | * UsageStatsManager to detect foreground package on API 21 and above.
102 | *
103 | * Package must be granted {@link android.Manifest.permission#PACKAGE_USAGE_STATS}
104 | * permission to detect foreground app on API 21 and above.
105 | */
106 | private UsageStatsManager mUsageStatsManager;
107 |
108 | /**
109 | * Constructor to initialize an object of this class.
110 | *
111 | * @param dynamicEngine The dynamic engine using which is using this task.
112 | */
113 | @SuppressLint("WrongConstant")
114 | public DynamicAppMonitor(@NonNull DynamicEngine dynamicEngine) {
115 | this.mDynamicEngine = dynamicEngine;
116 | this.mActivityManager = ContextCompat.getSystemService(
117 | dynamicEngine, ActivityManager.class);
118 |
119 | if (DynamicSdkUtils.is21()) {
120 | if (DynamicSdkUtils.is22()) {
121 | this.mUsageStatsManager = ContextCompat.getSystemService(
122 | dynamicEngine, UsageStatsManager.class);
123 | }
124 |
125 | if (mUsageStatsManager == null) {
126 | this.mUsageStatsManager = (UsageStatsManager)
127 | dynamicEngine.getSystemService(ADE_USAGE_STATS);
128 | }
129 | }
130 | }
131 |
132 | @Override
133 | protected void onPreExecute() {
134 | super.onPreExecute();
135 |
136 | mDynamicAppInfo = null;
137 | }
138 |
139 | @Override
140 | protected Void doInBackground(@Nullable Void params) {
141 | while (isRunning()) {
142 | try {
143 | if (!isPaused() && !isDormant()) {
144 | publishProgress(new DynamicResult.Progress<>(getForegroundAppInfo()));
145 | }
146 |
147 | Thread.sleep(ADE_NOTIFICATION_TIMEOUT);
148 | } catch (Exception ignored) {
149 | }
150 | }
151 |
152 | return null;
153 | }
154 |
155 | @Override
156 | protected void onProgressUpdate(@Nullable DynamicResult progress) {
157 | super.onProgressUpdate(progress);
158 |
159 | if (progress != null) {
160 | if (progress.getData() != null && progress.getData().getPackageName() != null
161 | && (mDynamicAppInfo == null || !progress.getData().equals(mDynamicAppInfo))) {
162 | mDynamicAppInfo = progress.getData();
163 | mDynamicEngine.getSpecialEventListener().onAppChange(mDynamicAppInfo);
164 | }
165 | }
166 | }
167 |
168 | @Override
169 | protected void onPostExecute(@Nullable DynamicResult result) {
170 | super.onPostExecute(result);
171 |
172 | mDynamicAppInfo = null;
173 | mDynamicEngine = null;
174 | }
175 |
176 | @Override
177 | protected void onCancelled() {
178 | super.onCancelled();
179 |
180 | onProgressUpdate(new DynamicResult.Progress<>(null));
181 |
182 | if (mDynamicEngine != null) {
183 | mDynamicEngine.getSpecialEventListener().onAppChange(mDynamicAppInfo);
184 | }
185 | }
186 |
187 | /**
188 | * This method will be called to notify for the accessibility event.
189 | *
190 | * @param event The accessibility event.
191 | */
192 | public void onAccessibilityEvent(@Nullable AccessibilityEvent event) {
193 | if (!isRunning() || isPaused() || event == null
194 | || event.getPackageName() == null || event.getClassName() == null) {
195 | return;
196 | }
197 |
198 | if (DynamicEngineUtils.getActivityInfo(mDynamicEngine, new ComponentName(
199 | event.getPackageName().toString(), event.getClassName().toString())) != null) {
200 | onProgressUpdate(new DynamicResult.Progress<>(DynamicEngineUtils.getAppInfoFromPackage(
201 | mDynamicEngine, event.getPackageName().toString())));
202 | }
203 | }
204 |
205 | /**
206 | * Get the running status of this task.
207 | *
208 | * @return {@code true} if this task is running.
209 | */
210 | public boolean isRunning() {
211 | return mRunning.get();
212 | }
213 |
214 | /**
215 | * Set the running status of this task.
216 | *
217 | * @param running {@code true} if this task is running.
218 | */
219 | public void setRunning(boolean running) {
220 | mRunning.set(running);
221 | }
222 |
223 | /**
224 | * Get the paused status of this task.
225 | *
226 | * @return {@code true} if this task is paused.
227 | */
228 | public boolean isPaused() {
229 | return mPaused.get();
230 | }
231 |
232 | /**
233 | * Set the paused status of this task.
234 | *
235 | * @param paused {@code true} if this task is paused.
236 | */
237 | public void setPaused(boolean paused) {
238 | mPaused.set(paused);
239 | }
240 |
241 | /**
242 | * Get the dormant status of this task.
243 | *
244 | * @return {@code true} if this task is dormant.
245 | */
246 | public boolean isDormant() {
247 | return mDormant.get();
248 | }
249 |
250 | /**
251 | * Set the dormant status of this task.
252 | *
253 | * @param dormant {@code true} if this task is dormant.
254 | */
255 | public void setDormant(boolean dormant) {
256 | mDormant.set(dormant);
257 | }
258 |
259 | /**
260 | * Get the current dynamic app info.
261 | *
262 | * @return The current dynamic app info.
263 | */
264 | public @Nullable DynamicAppInfo getCurrentAppInfo() {
265 | return mDynamicAppInfo;
266 | }
267 |
268 | /**
269 | * Set the current dynamic app info.
270 | *
271 | * @param dynamicAppInfo The current dynamic app info to be set.
272 | */
273 | public void setCurrentAppInfo(@Nullable DynamicAppInfo dynamicAppInfo) {
274 | this.mDynamicAppInfo = dynamicAppInfo;
275 | }
276 |
277 | /**
278 | * Retrieve the dynamic app info for the foreground package.
279 | *
280 | * @return The dynamic app info from the foreground package name.
281 | */
282 | private @Nullable DynamicAppInfo getForegroundAppInfo() {
283 | String packageName;
284 | if (DynamicSdkUtils.is21()) {
285 | packageName = DynamicEngineUtils.getForegroundPackage(mUsageStatsManager,
286 | System.currentTimeMillis(), ADE_USAGE_STATS_INTERVAL);
287 | } else {
288 | packageName = DynamicEngineUtils.getForegroundPackage(mActivityManager);
289 | }
290 |
291 | if (packageName != null) {
292 | return DynamicEngineUtils.getAppInfoFromPackage(mDynamicEngine, packageName);
293 | }
294 |
295 | return null;
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/util/DynamicEngineUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2024 Pranav Pandey
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.pranavpandey.android.dynamic.engine.util;
18 |
19 | import android.annotation.TargetApi;
20 | import android.app.ActivityManager;
21 | import android.app.usage.UsageEvents;
22 | import android.app.usage.UsageStatsManager;
23 | import android.content.ComponentName;
24 | import android.content.Context;
25 | import android.content.Intent;
26 | import android.content.IntentFilter;
27 | import android.content.pm.ActivityInfo;
28 | import android.content.pm.PackageManager;
29 | import android.os.Build;
30 |
31 | import androidx.annotation.NonNull;
32 | import androidx.annotation.Nullable;
33 |
34 | import com.pranavpandey.android.dynamic.engine.DynamicEngine;
35 | import com.pranavpandey.android.dynamic.engine.model.DynamicAppInfo;
36 | import com.pranavpandey.android.dynamic.util.DynamicSdkUtils;
37 |
38 | /**
39 | * Helper class used for the {@link DynamicEngine}.
40 | */
41 | public class DynamicEngineUtils {
42 |
43 | /**
44 | * Intent action constant for the on call state.
45 | */
46 | public static final String ACTION_ON_CALL =
47 | "com.pranavpandey.android.dynamic.engine.ACTION_ON_CALL";
48 |
49 | /**
50 | * Intent action constant for the call idle state.
51 | */
52 | public static final String ACTION_CALL_IDLE =
53 | "com.pranavpandey.android.dynamic.engine.ACTION_CALL_IDLE";
54 |
55 | /**
56 | * Constant for the package scheme.
57 | */
58 | private static final String PACKAGE_SCHEME = "package";
59 |
60 | /**
61 | * Android package name.
62 | */
63 | private static final String PACKAGE_ANDROID = "android";
64 |
65 | /**
66 | * Constant for the unknown event type.
67 | */
68 | private static final int EVENT_UNKNOWN = -1;
69 |
70 | /**
71 | * Returns activity info from the component name.
72 | *
73 | * @param context The context to get {@link PackageManager}.
74 | * @param componentName The component name to be used.
75 | *
76 | * @return The activity info from the component name.
77 | */
78 | public static @Nullable ActivityInfo getActivityInfo(
79 | @NonNull Context context, @Nullable ComponentName componentName) {
80 | if (componentName != null) {
81 | try {
82 | return context.getPackageManager().getActivityInfo(
83 | componentName, PackageManager.GET_META_DATA);
84 | } catch (Exception ignored) {
85 | }
86 | }
87 |
88 | return null;
89 | }
90 |
91 | /**
92 | * Load dynamic app info from the package name.
93 | *
94 | * @param context The context to get {@link PackageManager}.
95 | * @param packageName The package name to build the dynamic app info.
96 | *
97 | * @return The dynamic app info from the package name.
98 | */
99 | public static @Nullable DynamicAppInfo getAppInfoFromPackage(@NonNull Context context,
100 | @Nullable String packageName) {
101 | if (packageName != null) {
102 | DynamicAppInfo dynamicAppInfo = new DynamicAppInfo();
103 | try {
104 | dynamicAppInfo.setApplicationInfo(
105 | context.getPackageManager().getApplicationInfo(
106 | packageName, PackageManager.GET_META_DATA));
107 |
108 | dynamicAppInfo.setPackageName(packageName);
109 | if (dynamicAppInfo.getApplicationInfo() != null) {
110 | dynamicAppInfo.setLabel(dynamicAppInfo.getApplicationInfo().
111 | loadLabel(context.getPackageManager()).toString());
112 | }
113 | } catch (Exception ignored) {
114 | }
115 |
116 | return dynamicAppInfo;
117 | }
118 |
119 | return null;
120 | }
121 |
122 | /**
123 | * Returns the intent filter to register various events.
124 | *
125 | * @return The intent filter to register a broadcast receiver which can listen special
126 | * actions of the {@link DynamicEngine}.
127 | */
128 | public static @NonNull IntentFilter getEventsIntentFilter() {
129 | IntentFilter intentFilter = new IntentFilter();
130 | intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
131 | intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
132 | intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
133 | intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
134 | intentFilter.addAction(Intent.ACTION_USER_PRESENT);
135 | intentFilter.addAction(Intent.ACTION_SCREEN_ON);
136 | intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
137 |
138 | return intentFilter;
139 | }
140 |
141 | /**
142 | * Returns the intent filter to register the call event.
143 | *
144 | * @return The intent filter to register a broadcast receiver which can listen call events
145 | * of the {@link DynamicEngine}.
146 | */
147 | public static @NonNull IntentFilter getCallIntentFilter() {
148 | IntentFilter intentFilter = new IntentFilter();
149 | intentFilter.addAction(ACTION_ON_CALL);
150 | intentFilter.addAction(ACTION_CALL_IDLE);
151 |
152 | return intentFilter;
153 | }
154 |
155 | /**
156 | * Returns the intent filter to register package intent.
157 | *
158 | * @return The intent filter to register a broadcast receiver which can listen package
159 | * added or removed broadcasts.
160 | */
161 | public static @NonNull IntentFilter getPackageIntentFilter() {
162 | IntentFilter intentFilter = new IntentFilter();
163 | intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
164 | intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
165 | intentFilter.addDataScheme(PACKAGE_SCHEME);
166 |
167 | return intentFilter;
168 | }
169 |
170 | /**
171 | * Returns the correct type for the foreground event.
172 | *
173 | * @return The correct type for the foreground event.
174 | *
175 | * @see UsageEvents.Event#ACTIVITY_RESUMED
176 | * @see UsageEvents.Event#MOVE_TO_FOREGROUND
177 | */
178 | @SuppressWarnings("deprecation")
179 | @TargetApi(Build.VERSION_CODES.Q)
180 | public static int getForegroundEventType() {
181 | if (!DynamicSdkUtils.is21()) {
182 | return EVENT_UNKNOWN;
183 | }
184 |
185 | return DynamicSdkUtils.is29() ? UsageEvents.Event.ACTIVITY_RESUMED
186 | : UsageEvents.Event.MOVE_TO_FOREGROUND;
187 | }
188 |
189 | /**
190 | * Retrieve the foreground package.
191 | *
192 | * @param usageStatsManager The usage stats manager instance.
193 | * @param time The start time to get the recent apps.
194 | * @param interval The interval for the requested events.
195 | *
196 | * @return The foreground package name on API 21 and above.
197 | */
198 | @TargetApi(Build.VERSION_CODES.Q)
199 | public static @Nullable String getForegroundPackage(
200 | @Nullable UsageStatsManager usageStatsManager, long time, long interval) {
201 | if (!DynamicSdkUtils.is21() || usageStatsManager == null) {
202 | return null;
203 | }
204 |
205 | String packageName = null;
206 |
207 | try {
208 | UsageEvents usageEvents = usageStatsManager.queryEvents(time - interval, time);
209 | UsageEvents.Event event = new UsageEvents.Event();
210 |
211 | if (usageEvents != null) {
212 | while (usageEvents.hasNextEvent()) {
213 | usageEvents.getNextEvent(event);
214 |
215 | if (event.getEventType() == getForegroundEventType()
216 | && !PACKAGE_ANDROID.equals(event.getPackageName())) {
217 | packageName = event.getPackageName();
218 |
219 | break;
220 | }
221 | }
222 | }
223 | } catch (Exception e) {
224 | e.printStackTrace();
225 | }
226 |
227 | return packageName;
228 | }
229 |
230 | /**
231 | * Retrieve the foreground package.
232 | *
233 | * @param activityManager The activity manager instance.
234 | *
235 | * @return The foreground package name on API 20 and below.
236 | */
237 | @SuppressWarnings("deprecation")
238 | public static @Nullable String getForegroundPackage(
239 | @Nullable ActivityManager activityManager) {
240 | if (activityManager == null) {
241 | return null;
242 | }
243 |
244 | ActivityManager.RunningTaskInfo runningTaskInfo =
245 | activityManager.getRunningTasks(1).get(0);
246 |
247 | if (runningTaskInfo.topActivity != null) {
248 | return runningTaskInfo.topActivity.getPackageName();
249 | }
250 |
251 | return null;
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/dynamic-engine/src/main/res/values/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 | 200
22 |
23 |
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | android.nonTransitiveRClass=false
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pranavpandey/dynamic-engine/1e864b82b7905e184a76c5c66412a28c78b8060f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jan 29 03:26:28 IST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/graphics/apps/pranavpandey-rotation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pranavpandey/dynamic-engine/1e864b82b7905e184a76c5c66412a28c78b8060f/graphics/apps/pranavpandey-rotation.png
--------------------------------------------------------------------------------
/graphics/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pranavpandey/dynamic-engine/1e864b82b7905e184a76c5c66412a28c78b8060f/graphics/icon.png
--------------------------------------------------------------------------------
/graphics/icon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pranavpandey/dynamic-engine/1e864b82b7905e184a76c5c66412a28c78b8060f/graphics/icon.psd
--------------------------------------------------------------------------------
/graphics/legacy/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pranavpandey/dynamic-engine/1e864b82b7905e184a76c5c66412a28c78b8060f/graphics/legacy/icon.png
--------------------------------------------------------------------------------
/graphics/legacy/icon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pranavpandey/dynamic-engine/1e864b82b7905e184a76c5c66412a28c78b8060f/graphics/legacy/icon.psd
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2022 Pranav Pandey
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 | include ':dynamic-engine'
18 |
--------------------------------------------------------------------------------