diff --git a/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/DynamicEngine.java b/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/DynamicEngine.java index bd7d61b..65f1ac5 100644 --- a/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/DynamicEngine.java +++ b/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/DynamicEngine.java @@ -22,6 +22,10 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; import android.os.BatteryManager; import android.os.Build; @@ -33,10 +37,12 @@ import com.pranavpandey.android.dynamic.engine.listener.DynamicEventListener; import com.pranavpandey.android.dynamic.engine.model.DynamicAppInfo; import com.pranavpandey.android.dynamic.engine.model.DynamicEvent; +import com.pranavpandey.android.dynamic.engine.model.DynamicHinge; import com.pranavpandey.android.dynamic.engine.model.DynamicPriority; import com.pranavpandey.android.dynamic.engine.service.DynamicStickyService; import com.pranavpandey.android.dynamic.engine.task.DynamicAppMonitor; import com.pranavpandey.android.dynamic.engine.util.DynamicEngineUtils; +import com.pranavpandey.android.dynamic.util.DynamicDeviceUtils; import com.pranavpandey.android.dynamic.util.DynamicSdkUtils; import com.pranavpandey.android.dynamic.util.DynamicTaskUtils; @@ -55,8 +61,9 @@ *

Package must be granted {@link android.Manifest.permission#PACKAGE_USAGE_STATS} * permission to detect the foreground app on API 21 and above. */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1) -public abstract class DynamicEngine extends DynamicStickyService implements DynamicEventListener { +@TargetApi(Build.VERSION_CODES.R) +public abstract class DynamicEngine extends DynamicStickyService + implements SensorEventListener, DynamicEventListener { /** * Intent extra for headset state. @@ -64,20 +71,30 @@ public abstract class DynamicEngine extends DynamicStickyService implements Dyna private static final String ADE_EXTRA_HEADSET_STATE = "state"; /** - * Listener to listen special events. + * Sensor manager to register listeners. */ - private DynamicEventListener mDynamicEventListener; + private SensorManager mSensorManager; /** - * Broadcast receiver to receive special events. + * Keyguard manager to detect the lock screen state. */ - private SpecialEventReceiver mSpecialEventReceiver; + private KeyguardManager mKeyguardManager; /** * Task to monitor foreground app. */ private DynamicAppMonitor mDynamicAppMonitor; + /** + * Broadcast receiver to receive special events. + */ + private SpecialEventReceiver mSpecialEventReceiver; + + /** + * The dynamic hinge state. + */ + private @DynamicHinge int mHinge; + /** * {@code true} if the device is on call. Either ringing or answered. */ @@ -108,11 +125,6 @@ public abstract class DynamicEngine extends DynamicStickyService implements Dyna */ private boolean mDocked; - /** - * Keyguard manager to detect the lock screen state. - */ - private KeyguardManager mKeyguardManager; - /** * Array list to store the events priority. */ @@ -127,15 +139,14 @@ public abstract class DynamicEngine extends DynamicStickyService implements Dyna public void onCreate() { super.onCreate(); - mDynamicEventListener = this; - mSpecialEventReceiver = new SpecialEventReceiver(); - mDynamicAppMonitor = new DynamicAppMonitor(this); + mSensorManager = ContextCompat.getSystemService(this, SensorManager.class); mKeyguardManager = ContextCompat.getSystemService(this, KeyguardManager.class); - + mDynamicAppMonitor = new DynamicAppMonitor(this); + mSpecialEventReceiver = new SpecialEventReceiver(); + registerReceiver(mSpecialEventReceiver, DynamicEngineUtils.getEventsIntentFilter()); registerReceiver(mSpecialEventReceiver, DynamicEngineUtils.getPackageIntentFilter()); registerReceiver(mSpecialEventReceiver, DynamicEngineUtils.getCallIntentFilter()); - updateEventsPriority(); mEventsMap = new LinkedHashMap<>(); @@ -146,6 +157,14 @@ public void onCreate() { * Initialize special events and check for some already occurred and ongoing events. */ public void initializeEvents() { + if (DynamicDeviceUtils.hasHingeFeature(this)) { + mSensorManager.registerListener(this, + mSensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE), + SensorManager.SENSOR_DELAY_NORMAL); + } else { + setHinge(DynamicHinge.UNKNOWN); + } + Intent chargingIntent = registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); if (chargingIntent != null) { @@ -167,7 +186,7 @@ public void initializeEvents() { != Intent.EXTRA_DOCK_STATE_UNDOCKED; } - mDynamicEventListener.onInitialize(mCharging, mHeadset, mDocked); + onInitialize(mCharging, mHeadset, mDocked); } /** @@ -191,13 +210,22 @@ public void updateEventsMap(@DynamicEvent @NonNull String event, boolean active) } } + /** + * Get the sensor manager used by this service. + * + * @return The sensor manager used by this service. + */ + public @NonNull SensorManager getSensorManager() { + return mSensorManager; + } + /** * Get the listener to listen special events. * * @return The listener to listen special events. */ public @NonNull DynamicEventListener getSpecialEventListener() { - return mDynamicEventListener; + return this; } /** @@ -253,11 +281,27 @@ public void onDestroy() { try { unregisterReceiver(mSpecialEventReceiver); setAppMonitorTask(false); + + if (DynamicSdkUtils.is30()) { + mSensorManager.unregisterListener(this); + } } catch (Exception ignored) { } super.onDestroy(); } + public @DynamicHinge int getHinge() { + return mHinge; + } + + public void setHinge(@DynamicHinge int hinge) { + if (hinge != getHinge()) { + this.mHinge = hinge; + + onHingeStateChange(hinge); + } + } + /** * Get the status of call event. * @@ -277,7 +321,8 @@ public boolean isCall() { public void setCall(boolean call) { if (call != isCall()) { this.mCall = call; - mDynamicEventListener.onCallStateChange(call); + + onCallStateChange(call); } } @@ -298,7 +343,8 @@ public boolean isScreenOff() { public void setScreenOff(boolean screenOff) { if (screenOff != isScreenOff()) { this.mScreenOff = screenOff; - mDynamicEventListener.onScreenStateChange(screenOff); + + onScreenStateChange(screenOff); } } @@ -320,7 +366,8 @@ public boolean isLocked() { public void setLocked(boolean locked) { if (locked != isLocked()) { this.mLocked = locked; - mDynamicEventListener.onLockStateChange(locked); + + onLockStateChange(locked); } } @@ -342,7 +389,8 @@ public boolean isHeadset() { public void setHeadset(boolean headset) { if (headset != isHeadset()) { this.mHeadset = headset; - mDynamicEventListener.onHeadsetStateChange(headset); + + onHeadsetStateChange(headset); } } @@ -361,9 +409,10 @@ public boolean isCharging() { * @param charging {@code true} if the device is charging or connected to a power source. */ public void setCharging(boolean charging) { - if (charging != mCharging) { + if (charging != isCharging()) { this.mCharging = charging; - mDynamicEventListener.onChargingStateChange(charging); + + onChargingStateChange(charging); } } @@ -384,10 +433,35 @@ public boolean isDocked() { public void setDocked(boolean docked) { if (docked != isDocked()) { this.mDocked = docked; - mDynamicEventListener.onDockStateChange(docked); + + onDockStateChange(docked); + } + } + + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + if (DynamicSdkUtils.is30() + && sensorEvent.sensor.getType() == Sensor.TYPE_HINGE_ANGLE) { + final float[] value = sensorEvent.values; + if (value == null || value.length == 0) { + return; + } + + if ((value[0] >= 90 && value[0] < 150) || + (value[0] > 180 && value[0] <= 270)) { + setHinge(DynamicHinge.HALF_EXPANDED); + } else if ((value[0] >= 150 && value[0] <= 180) + || (value[0] > 270 && value[0] < 360)) { + setHinge(DynamicHinge.FLAT); + } else { + setHinge(DynamicHinge.COLLAPSED); + } } } + @Override + public void onAccuracyChanged(Sensor sensor, int i) { } + /** * Broadcast receiver to listen various events. * @@ -451,17 +525,15 @@ public void onReceive(@NonNull Context context, @Nullable Intent intent) { Intent.EXTRA_REPLACING, false); if (!isReplacing) { - mDynamicEventListener.onPackageRemoved( - intent.getData().getSchemeSpecificPart()); + onPackageRemoved(intent.getData().getSchemeSpecificPart()); } } break; case Intent.ACTION_PACKAGE_ADDED: if (intent.getData() != null && intent.getData().getSchemeSpecificPart() != null) { - mDynamicEventListener.onPackageUpdated( - DynamicEngineUtils.getAppInfoFromPackage(context, - intent.getData().getSchemeSpecificPart()), !isReplacing); + onPackageUpdated(DynamicEngineUtils.getAppInfoFromPackage(context, + intent.getData().getSchemeSpecificPart()), !isReplacing); } break; case DynamicEngineUtils.ACTION_ON_CALL: @@ -498,6 +570,10 @@ public void onInitialize(boolean charging, boolean headset, boolean docked) { updateEventsMap(DynamicEvent.DOCK, docked); } + @CallSuper + @Override + public void onHingeStateChange(@DynamicHinge int state) { } + @CallSuper @Override public void onCallStateChange(boolean call) { diff --git a/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/listener/DynamicEventListener.java b/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/listener/DynamicEventListener.java index 6618a26..3eb62a2 100644 --- a/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/listener/DynamicEventListener.java +++ b/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/listener/DynamicEventListener.java @@ -20,6 +20,7 @@ import com.pranavpandey.android.dynamic.engine.DynamicEngine; import com.pranavpandey.android.dynamic.engine.model.DynamicAppInfo; +import com.pranavpandey.android.dynamic.engine.model.DynamicHinge; /** * Interface to listen various system events with the help of {@link DynamicEngine}. @@ -38,7 +39,16 @@ public interface DynamicEventListener { void onInitialize(boolean charging, boolean headset, boolean docked); /** - * This method will be called when call state is changed. + * This method will be called when the hinge state is changed. + * + * @param state The current hinge state. + * + * @see DynamicHinge + */ + void onHingeStateChange(@DynamicHinge int state); + + /** + * This method will be called when the call state is changed. *

Either on call or the device is idle. * * @param call {@code true} if the device is on call. @@ -47,7 +57,7 @@ public interface DynamicEventListener { void onCallStateChange(boolean call); /** - * This method will be called when screen state is changed. + * This method will be called when the screen state is changed. *

Either the device screen is off or on. * * @param screenOff {@code true} if the device screen is off. @@ -55,7 +65,7 @@ public interface DynamicEventListener { void onScreenStateChange(boolean screenOff); /** - * This method will be called when lock state is changed. + * This method will be called when the lock state is changed. *

Either the device is in the locked or unlocked state independent of the PIN, * password or any other security lock. * @@ -64,7 +74,7 @@ public interface DynamicEventListener { void onLockStateChange(boolean locked); /** - * This method will be called when headset state is changed. + * This method will be called when the headset state is changed. *

Either the device is connected to a audio output device or volume is routed through * the internal speaker. * @@ -74,7 +84,7 @@ public interface DynamicEventListener { void onHeadsetStateChange(boolean connected); /** - * This method will be called when charging state is changed. + * This method will be called when the charging state is changed. *

Either the device is connected to a power source using the battery. * * @param charging {@code true} if the device is charging or connected to a power source. @@ -82,7 +92,7 @@ public interface DynamicEventListener { void onChargingStateChange(boolean charging); /** - * This method will be called when dock state is changed. + * This method will be called when the dock state is changed. *

Either the device is docked or not. * * @param docked {@code true} if the device is docked. @@ -90,7 +100,7 @@ public interface DynamicEventListener { void onDockStateChange(boolean docked); /** - * This method will be called when foreground app is changed. + * This method will be called when the foreground app is changed. *

Use it to provide the app specific functionality in the app. * * @param dynamicAppInfo The dynamic app info of the foreground package. diff --git a/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/model/DynamicHinge.java b/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/model/DynamicHinge.java new file mode 100644 index 0000000..b3a95fd --- /dev/null +++ b/dynamic-engine/src/main/java/com/pranavpandey/android/dynamic/engine/model/DynamicHinge.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017-2022 Pranav Pandey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.pranavpandey.android.dynamic.engine.model; + +import com.pranavpandey.android.dynamic.engine.DynamicEngine; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Hinge states supported by the {@link DynamicEngine}. + */ +@Retention(RetentionPolicy.SOURCE) +public @interface DynamicHinge { + + /** + * Constant for the unknown hinge state. + */ + int UNKNOWN = -1; + + /** + * Constant for the collapsed hinge state. + */ + int COLLAPSED = 0; + + /** + * Constant for the half expanded hinge state. + */ + int HALF_EXPANDED = 1; + + /** + * Constant for the flat hinge state. + */ + int FLAT = 2; +}