diff --git a/msmnile.mk b/msmnile.mk index 2580c56..98c3483 100644 --- a/msmnile.mk +++ b/msmnile.mk @@ -142,6 +142,10 @@ PRODUCT_PACKAGES += \ android.hardware.ir@1.0-impl \ android.hardware.ir@1.0-service +# Device-specific settings +PRODUCT_PACKAGES += \ + XiaomiParts + # Display PRODUCT_PACKAGES += \ android.hardware.graphics.composer@2.4-impl \ diff --git a/parts/Android.bp b/parts/Android.bp new file mode 100644 index 0000000..3967abc --- /dev/null +++ b/parts/Android.bp @@ -0,0 +1,25 @@ +// +// Copyright (C) 2017-2022 The LineageOS Project +// +// SPDX-License-Identifier: Apache-2.0 +// + +android_app { + name: "XiaomiParts", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + certificate: "platform", + platform_apis: true, + system_ext_specific: true, + privileged: true, + + static_libs: [ + "org.lineageos.settings.resources", + "//hardware/xiaomi:vendor.xiaomi.hardware.motor-V1.0-java", + ], + + optimize: { + proguard_flags_files: ["proguard.flags"], + }, +} diff --git a/parts/AndroidManifest.xml b/parts/AndroidManifest.xml new file mode 100644 index 0000000..4e78b11 --- /dev/null +++ b/parts/AndroidManifest.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/parts/proguard.flags b/parts/proguard.flags new file mode 100644 index 0000000..0abe5d3 --- /dev/null +++ b/parts/proguard.flags @@ -0,0 +1,7 @@ +-keep class org.lineageos.settings.doze.* { + *; +} + +-keep class org.lineageos.settings.popupcamera.* { + *; +} diff --git a/parts/res/drawable/ic_hand.xml b/parts/res/drawable/ic_hand.xml new file mode 100644 index 0000000..5f56669 --- /dev/null +++ b/parts/res/drawable/ic_hand.xml @@ -0,0 +1,9 @@ + + + + diff --git a/parts/res/drawable/ic_pickup.xml b/parts/res/drawable/ic_pickup.xml new file mode 100644 index 0000000..a7d5eb7 --- /dev/null +++ b/parts/res/drawable/ic_pickup.xml @@ -0,0 +1,9 @@ + + + + diff --git a/parts/res/drawable/ic_pocket.xml b/parts/res/drawable/ic_pocket.xml new file mode 100644 index 0000000..44ae6e2 --- /dev/null +++ b/parts/res/drawable/ic_pocket.xml @@ -0,0 +1,9 @@ + + + + diff --git a/parts/res/drawable/ic_settings_popup.xml b/parts/res/drawable/ic_settings_popup.xml new file mode 100644 index 0000000..d593eb2 --- /dev/null +++ b/parts/res/drawable/ic_settings_popup.xml @@ -0,0 +1,11 @@ + + + + diff --git a/parts/res/values/arrays.xml b/parts/res/values/arrays.xml new file mode 100644 index 0000000..9f3ac5e --- /dev/null +++ b/parts/res/values/arrays.xml @@ -0,0 +1,53 @@ + + + + + + popup_muqin_up.ogg + popup_muqin_down.ogg + popup_yingyan_up.ogg + popup_yingyan_down.ogg + popup_mofa_up.ogg + popup_mofa_down.ogg + popup_jijia_up.ogg + popup_jijia_down.ogg + popup_chilun_up.ogg + popup_chilun_down.ogg + popup_cangmen_up.ogg + popup_cangmen_down.ogg + + + + @string/action_none + @string/popup_title_muqin + @string/popup_title_yingyan + @string/popup_title_mofa + @string/popup_title_jijia + @string/popup_title_chilun + @string/popup_title_cangmen + + + + -1 + 0 + 2 + 4 + 6 + 8 + 10 + + diff --git a/parts/res/values/strings.xml b/parts/res/values/strings.xml new file mode 100644 index 0000000..22e2d35 --- /dev/null +++ b/parts/res/values/strings.xml @@ -0,0 +1,43 @@ + + + + + Use Ambient display + + + Camera LED + Show LED light when the front camera appears and retracts + Front camera sound effects + Xylophone + Condor + Magic + Mecha + Gearwheel + Cabin door + + + Warning + Couldn\'t close front camera multiple times. Try calibrating the camera. + Couldn\'t open front camera multiple times. Try calibrating the camera. + Front camera cannot be used during calibration. + Calibrate + Couldn\'t calibrate + Calibrated successfully. You can open the front camera now. + You\'re opening the front camera too frequently. + Couldn\'t close front camera. Try again. + Couldn\'t open front camera. Try again. + diff --git a/parts/res/xml/doze_settings.xml b/parts/res/xml/doze_settings.xml new file mode 100644 index 0000000..682efe1 --- /dev/null +++ b/parts/res/xml/doze_settings.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/parts/res/xml/popup_settings.xml b/parts/res/xml/popup_settings.xml new file mode 100644 index 0000000..16c19c8 --- /dev/null +++ b/parts/res/xml/popup_settings.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/parts/src/org/lineageos/settings/BootCompletedReceiver.java b/parts/src/org/lineageos/settings/BootCompletedReceiver.java new file mode 100644 index 0000000..8bd6644 --- /dev/null +++ b/parts/src/org/lineageos/settings/BootCompletedReceiver.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2017-2020 The LineageOS Project + * + * 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 org.lineageos.settings; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import org.lineageos.settings.doze.DozeUtils; +import org.lineageos.settings.popupcamera.PopupCameraUtils; + +public class BootCompletedReceiver extends BroadcastReceiver { + + private static final boolean DEBUG = false; + private static final String TAG = "XiaomiParts"; + + @Override + public void onReceive(final Context context, Intent intent) { + if (DEBUG) Log.d(TAG, "Received boot completed intent"); + DozeUtils.checkDozeService(context); + PopupCameraUtils.checkPopupCameraService(context); + } +} diff --git a/parts/src/org/lineageos/settings/doze/DozeService.java b/parts/src/org/lineageos/settings/doze/DozeService.java new file mode 100644 index 0000000..671cc5d --- /dev/null +++ b/parts/src/org/lineageos/settings/doze/DozeService.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2017-2020 The LineageOS Project + * + * 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 org.lineageos.settings.doze; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.IBinder; +import android.util.Log; + +import org.lineageos.settings.sensors.PickupSensor; +import org.lineageos.settings.sensors.ProximitySensor; + +public class DozeService extends Service { + private static final String TAG = "DozeService"; + private static final boolean DEBUG = false; + + private ProximitySensor mProximitySensor; + private PickupSensor mPickupSensor; + private BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { + onDisplayOn(); + } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { + onDisplayOff(); + } + } + }; + + @Override + public void onCreate() { + if (DEBUG) Log.d(TAG, "Creating service"); + mProximitySensor = new ProximitySensor(this); + mPickupSensor = new PickupSensor(this); + + IntentFilter screenStateFilter = new IntentFilter(); + screenStateFilter.addAction(Intent.ACTION_SCREEN_ON); + screenStateFilter.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(mScreenStateReceiver, screenStateFilter); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (DEBUG) Log.d(TAG, "Starting service"); + return START_STICKY; + } + + @Override + public void onDestroy() { + if (DEBUG) Log.d(TAG, "Destroying service"); + super.onDestroy(); + this.unregisterReceiver(mScreenStateReceiver); + mProximitySensor.disable(); + mPickupSensor.disable(); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private void onDisplayOn() { + if (DEBUG) Log.d(TAG, "Display on"); + if (DozeUtils.isPickUpEnabled(this)) { + mPickupSensor.disable(); + } + if (DozeUtils.isHandwaveGestureEnabled(this) || + DozeUtils.isPocketGestureEnabled(this)) { + mProximitySensor.disable(); + } + } + + private void onDisplayOff() { + if (DEBUG) Log.d(TAG, "Display off"); + if (DozeUtils.isPickUpEnabled(this)) { + mPickupSensor.enable(); + } + if (DozeUtils.isHandwaveGestureEnabled(this) || + DozeUtils.isPocketGestureEnabled(this)) { + mProximitySensor.enable(); + } + } +} diff --git a/parts/src/org/lineageos/settings/doze/DozeSettingsActivity.java b/parts/src/org/lineageos/settings/doze/DozeSettingsActivity.java new file mode 100644 index 0000000..213f0b4 --- /dev/null +++ b/parts/src/org/lineageos/settings/doze/DozeSettingsActivity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015-2016 The CyanogenMod Project + * 2017,2021 The LineageOS Project + * + * 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 org.lineageos.settings.doze; + +import android.os.Bundle; + +import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; +import com.android.settingslib.collapsingtoolbar.R; + +public class DozeSettingsActivity extends CollapsingToolbarBaseActivity { + + private static final String TAG_DOZE = "doze"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getFragmentManager().beginTransaction().replace(R.id.content_frame, + new DozeSettingsFragment(), TAG_DOZE).commit(); + } +} diff --git a/parts/src/org/lineageos/settings/doze/DozeSettingsFragment.java b/parts/src/org/lineageos/settings/doze/DozeSettingsFragment.java new file mode 100644 index 0000000..3da577c --- /dev/null +++ b/parts/src/org/lineageos/settings/doze/DozeSettingsFragment.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2017-2019,2021 The LineageOS Project + * + * 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 org.lineageos.settings.doze; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.widget.Switch; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceFragment; +import androidx.preference.SwitchPreference; + +import com.android.settingslib.widget.MainSwitchPreference; +import com.android.settingslib.widget.OnMainSwitchChangeListener; + +import org.lineageos.settings.R; + +public class DozeSettingsFragment extends PreferenceFragment implements + Preference.OnPreferenceChangeListener, OnMainSwitchChangeListener { + + private MainSwitchPreference mSwitchBar; + + private SwitchPreference mAlwaysOnDisplayPreference; + + private SwitchPreference mPickUpPreference; + private SwitchPreference mHandwavePreference; + private SwitchPreference mPocketPreference; + + private Handler mHandler = new Handler(); + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.doze_settings); + + SharedPreferences prefs = getActivity().getSharedPreferences("doze_settings", + Activity.MODE_PRIVATE); + if (savedInstanceState == null && !prefs.getBoolean("first_help_shown", false)) { + showHelp(); + } + + boolean dozeEnabled = DozeUtils.isDozeEnabled(getActivity()); + + mSwitchBar = (MainSwitchPreference) findPreference(DozeUtils.DOZE_ENABLE); + mSwitchBar.addOnSwitchChangeListener(this); + mSwitchBar.setChecked(dozeEnabled); + + mAlwaysOnDisplayPreference = (SwitchPreference) findPreference(DozeUtils.ALWAYS_ON_DISPLAY); + mAlwaysOnDisplayPreference.setEnabled(dozeEnabled); + mAlwaysOnDisplayPreference.setChecked(DozeUtils.isAlwaysOnEnabled(getActivity())); + mAlwaysOnDisplayPreference.setOnPreferenceChangeListener(this); + + PreferenceCategory pickupSensorCategory = (PreferenceCategory) getPreferenceScreen(). + findPreference(DozeUtils.CATEG_PICKUP_SENSOR); + PreferenceCategory proximitySensorCategory = (PreferenceCategory) getPreferenceScreen(). + findPreference(DozeUtils.CATEG_PROX_SENSOR); + + mPickUpPreference = (SwitchPreference) findPreference(DozeUtils.GESTURE_PICK_UP_KEY); + mPickUpPreference.setEnabled(dozeEnabled); + mPickUpPreference.setOnPreferenceChangeListener(this); + + mHandwavePreference = (SwitchPreference) findPreference(DozeUtils.GESTURE_HAND_WAVE_KEY); + mHandwavePreference.setEnabled(dozeEnabled); + mHandwavePreference.setOnPreferenceChangeListener(this); + + mPocketPreference = (SwitchPreference) findPreference(DozeUtils.GESTURE_POCKET_KEY); + mPocketPreference.setEnabled(dozeEnabled); + mPocketPreference.setOnPreferenceChangeListener(this); + + // Hide proximity sensor related features if the device doesn't support them + if (!DozeUtils.getProxCheckBeforePulse(getActivity())) { + getPreferenceScreen().removePreference(proximitySensorCategory); + } + + // Hide AOD if not supported and set all its dependents otherwise + if (!DozeUtils.alwaysOnDisplayAvailable(getActivity())) { + getPreferenceScreen().removePreference(mAlwaysOnDisplayPreference); + } else { + pickupSensorCategory.setDependency(DozeUtils.ALWAYS_ON_DISPLAY); + proximitySensorCategory.setDependency(DozeUtils.ALWAYS_ON_DISPLAY); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (DozeUtils.ALWAYS_ON_DISPLAY.equals(preference.getKey())) { + DozeUtils.enableAlwaysOn(getActivity(), (Boolean) newValue); + } + + mHandler.post(() -> DozeUtils.checkDozeService(getActivity())); + + return true; + } + + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + DozeUtils.enableDoze(getActivity(), isChecked); + DozeUtils.checkDozeService(getActivity()); + + mSwitchBar.setChecked(isChecked); + + if (!isChecked) { + DozeUtils.enableAlwaysOn(getActivity(), false); + mAlwaysOnDisplayPreference.setChecked(false); + } + mAlwaysOnDisplayPreference.setEnabled(isChecked); + + mPickUpPreference.setEnabled(isChecked); + mHandwavePreference.setEnabled(isChecked); + mPocketPreference.setEnabled(isChecked); + } + + private void showHelp() { + HelpDialogFragment fragment = new HelpDialogFragment(); + fragment.show(getFragmentManager(), "help_dialog"); + } + + private static class HelpDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.doze_settings_help_title) + .setMessage(R.string.doze_settings_help_text) + .setNegativeButton(R.string.dialog_ok, (dialog, which) -> dialog.cancel()) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + getActivity().getSharedPreferences("doze_settings", Activity.MODE_PRIVATE) + .edit() + .putBoolean("first_help_shown", true) + .commit(); + } + } +} diff --git a/parts/src/org/lineageos/settings/doze/DozeUtils.java b/parts/src/org/lineageos/settings/doze/DozeUtils.java new file mode 100644 index 0000000..cc7cff1 --- /dev/null +++ b/parts/src/org/lineageos/settings/doze/DozeUtils.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2017-2019,2021 The LineageOS Project + * + * 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 org.lineageos.settings.doze; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.display.AmbientDisplayConfiguration; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import androidx.preference.PreferenceManager; + +import static android.provider.Settings.Secure.DOZE_ALWAYS_ON; +import static android.provider.Settings.Secure.DOZE_ENABLED; + +public final class DozeUtils { + + protected static final String DOZE_ENABLE = "doze_enable"; + + protected static final String ALWAYS_ON_DISPLAY = "always_on_display"; + protected static final String CATEG_PICKUP_SENSOR = "pickup_sensor"; + protected static final String CATEG_PROX_SENSOR = "proximity_sensor"; + protected static final String GESTURE_PICK_UP_KEY = "gesture_pick_up"; + protected static final String GESTURE_HAND_WAVE_KEY = "gesture_hand_wave"; + protected static final String GESTURE_POCKET_KEY = "gesture_pocket"; + private static final String TAG = "DozeUtils"; + private static final boolean DEBUG = false; + private static final String DOZE_INTENT = "com.android.systemui.doze.pulse"; + + public static void startService(Context context) { + if (DEBUG) Log.d(TAG, "Starting service"); + context.startServiceAsUser(new Intent(context, DozeService.class), + UserHandle.CURRENT); + } + + protected static void stopService(Context context) { + if (DEBUG) Log.d(TAG, "Stopping service"); + context.stopServiceAsUser(new Intent(context, DozeService.class), + UserHandle.CURRENT); + } + + public static void checkDozeService(Context context) { + if (isDozeEnabled(context) && !isAlwaysOnEnabled(context) && sensorsEnabled(context)) { + startService(context); + } else { + stopService(context); + } + } + + protected static boolean getProxCheckBeforePulse(Context context) { + try { + Context con = context.createPackageContext("com.android.systemui", 0); + int id = con.getResources().getIdentifier("doze_proximity_check_before_pulse", + "bool", "com.android.systemui"); + return con.getResources().getBoolean(id); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + protected static boolean enableDoze(Context context, boolean enable) { + return Settings.Secure.putInt(context.getContentResolver(), + DOZE_ENABLED, enable ? 1 : 0); + } + + public static boolean isDozeEnabled(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + DOZE_ENABLED, 1) != 0; + } + + public static void launchDozePulse(Context context) { + if (DEBUG) Log.d(TAG, "Launch doze pulse"); + context.sendBroadcastAsUser(new Intent(DOZE_INTENT), + new UserHandle(UserHandle.USER_CURRENT)); + } + + protected static boolean enableAlwaysOn(Context context, boolean enable) { + return Settings.Secure.putIntForUser(context.getContentResolver(), + DOZE_ALWAYS_ON, enable ? 1 : 0, UserHandle.USER_CURRENT); + } + + protected static boolean isAlwaysOnEnabled(Context context) { + final boolean enabledByDefault = context.getResources() + .getBoolean(com.android.internal.R.bool.config_dozeAlwaysOnEnabled); + + return Settings.Secure.getIntForUser(context.getContentResolver(), + DOZE_ALWAYS_ON, alwaysOnDisplayAvailable(context) && enabledByDefault ? 1 : 0, + UserHandle.USER_CURRENT) != 0; + } + + protected static boolean alwaysOnDisplayAvailable(Context context) { + return new AmbientDisplayConfiguration(context).alwaysOnAvailable(); + } + + protected static boolean isGestureEnabled(Context context, String gesture) { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(gesture, false); + } + + public static boolean isPickUpEnabled(Context context) { + return isGestureEnabled(context, GESTURE_PICK_UP_KEY); + } + + public static boolean isHandwaveGestureEnabled(Context context) { + return isGestureEnabled(context, GESTURE_HAND_WAVE_KEY); + } + + public static boolean isPocketGestureEnabled(Context context) { + return isGestureEnabled(context, GESTURE_POCKET_KEY); + } + + public static boolean sensorsEnabled(Context context) { + return isPickUpEnabled(context) || isHandwaveGestureEnabled(context) + || isPocketGestureEnabled(context); + } +} diff --git a/parts/src/org/lineageos/settings/popupcamera/Constants.java b/parts/src/org/lineageos/settings/popupcamera/Constants.java new file mode 100644 index 0000000..1c88c82 --- /dev/null +++ b/parts/src/org/lineageos/settings/popupcamera/Constants.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 The LineageOS Project + * + * 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 org.lineageos.settings.popupcamera; + +public class Constants { + public static final int CAMERA_EVENT_DELAY_TIME = 100; // ms + public static final int MSG_CAMERA_CLOSED = 1001; + public static final int MSG_CAMERA_OPEN = 1002; + + public static final int MOTOR_STATUS_CALIB_ERROR = 18; + public static final int MOTOR_STATUS_CALIB_OK = 17; + public static final int MOTOR_STATUS_CANCELED = 20; + public static final int MOTOR_STATUS_DROPPED = 16; + public static final int MOTOR_STATUS_INVALID = 10; + public static final int MOTOR_STATUS_MAX = 20; + public static final int MOTOR_STATUS_MIN = 10; + public static final int MOTOR_STATUS_NOT_INIT = -1; + public static final int MOTOR_STATUS_POPUP_JAMMED = 12; + public static final int MOTOR_STATUS_POPUP_OK = 11; + public static final int MOTOR_STATUS_PRESSED = 15; + public static final int MOTOR_STATUS_REQUEST_CALIB = 19; + public static final int MOTOR_STATUS_TAKEBACK_JAMMED = 14; + public static final int MOTOR_STATUS_TAKEBACK_OK = 13; + + public static final String CLOSE_CAMERA_STATE = "0"; + public static final String OPEN_CAMERA_STATE = "1"; + + public static final String FRONT_CAMERA_ID = "1"; + public static final String BLUE_LED_PATH = "/sys/class/leds/blue/breath"; + public static final String GREEN_LED_PATH = + "/sys/class/leds/green/breath"; + public static final String RED_LED_PATH = "/sys/class/leds/red/breath"; + public static final String BLUE_RIGHT_LED_PATH = + "/sys/class/leds/blue-right/breath"; + public static final String GREEN_RIGHT_LED_PATH = + "/sys/class/leds/green-right/breath"; + public static final String RED_RIGHT_LED_PATH = + "/sys/class/leds/red-right/breath"; + public static final String POPUP_SOUND_PATH = "/system_ext/media/audio/ui/"; +} diff --git a/parts/src/org/lineageos/settings/popupcamera/PopupCameraPreferences.java b/parts/src/org/lineageos/settings/popupcamera/PopupCameraPreferences.java new file mode 100644 index 0000000..fe5ae33 --- /dev/null +++ b/parts/src/org/lineageos/settings/popupcamera/PopupCameraPreferences.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The LineageOS Project + * + * 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 org.lineageos.settings.popupcamera; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public class PopupCameraPreferences { + + private static final String TAG = "PopupCameraUtils"; + private static final boolean DEBUG = false; + private static final String LED_EFFECT_KEY = "popup_led_effect"; + private static final boolean LED_EFFECT_DEFAULT_VALUE = true; + private static final String SOUND_EFFECT_KEY = "popup_sound_effect"; + private static final String SOUND_EFFECT_DEFAULT_VALUE = "0"; + private SharedPreferences mSharedPrefs; + + public PopupCameraPreferences(Context context) { + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + public String getSoundEffect() { + return mSharedPrefs.getString(SOUND_EFFECT_KEY, SOUND_EFFECT_DEFAULT_VALUE); + } + + public boolean isLedAllowed() { + return mSharedPrefs.getBoolean(LED_EFFECT_KEY, LED_EFFECT_DEFAULT_VALUE); + } +} diff --git a/parts/src/org/lineageos/settings/popupcamera/PopupCameraService.java b/parts/src/org/lineageos/settings/popupcamera/PopupCameraService.java new file mode 100644 index 0000000..8c6d9c2 --- /dev/null +++ b/parts/src/org/lineageos/settings/popupcamera/PopupCameraService.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2019 The LineageOS Project + * + * 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 org.lineageos.settings.popupcamera; + +import android.annotation.NonNull; +import android.app.AlertDialog; +import android.app.Service; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.camera2.CameraManager; +import android.media.AudioAttributes; +import android.media.SoundPool; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.Log; +import android.view.WindowManager; +import android.widget.Toast; + +import org.lineageos.settings.R; +import org.lineageos.settings.sensors.ProximitySensor; +import org.lineageos.settings.sensors.SensorsUtils; +import org.lineageos.settings.utils.FileUtils; + +import vendor.xiaomi.hardware.motor.V1_0.IMotor; +import vendor.xiaomi.hardware.motor.V1_0.IMotorCallback; +import vendor.xiaomi.hardware.motor.V1_0.MotorEvent; + +import java.util.NoSuchElementException; + +public class PopupCameraService extends Service implements Handler.Callback { + + private static final String TAG = "PopupCameraService"; + private static final boolean DEBUG = false; + + private IMotor mMotor = null; + + private final Object mLock = new Object(); + private boolean mMotorBusy = false; + private long mClosedEvent; + private long mOpenEvent; + private Handler mHandler = new Handler(this); + private boolean mMotorCalibrating = false; + private boolean mErrorDialogShowing; + + private PopupCameraPreferences mPopupCameraPreferences; + + private SensorManager mSensorManager; + private Sensor mFreeFallSensor; + private ProximitySensor mProximitySensor; + + private int[] mSounds; + private SoundPool mSoundPool; + + private IMotorCallback mMotorCallback = new IMotorCallback.Stub() { + @Override + public void onNotify(MotorEvent event) { + int status = event.vaalue; + int cookie = event.cookie; + if (DEBUG) + Log.d(TAG, "onNotify: cookie=" + cookie + ",status=" + status); + synchronized (mLock) { + if (status == Constants.MOTOR_STATUS_CALIB_OK || + status == Constants.MOTOR_STATUS_CALIB_ERROR) { + mMotorCalibrating = false; + showCalibrationResult(status); + } else if (status == Constants.MOTOR_STATUS_PRESSED) { + updateMotor(Constants.CLOSE_CAMERA_STATE); + goBackHome(); + } else if (status == Constants.MOTOR_STATUS_POPUP_JAMMED || + status == Constants.MOTOR_STATUS_TAKEBACK_JAMMED) { + showErrorDialog(); + } + } + } + }; + + private CameraManager.AvailabilityCallback mAvailabilityCallback = + new CameraManager.AvailabilityCallback() { + @Override + public void onCameraClosed(@NonNull String cameraId) { + super.onCameraClosed(cameraId); + if (cameraId.equals(Constants.FRONT_CAMERA_ID)) { + mClosedEvent = SystemClock.elapsedRealtime(); + if (SystemClock.elapsedRealtime() - mOpenEvent < + Constants.CAMERA_EVENT_DELAY_TIME && + mHandler.hasMessages(Constants.MSG_CAMERA_OPEN)) { + mHandler.removeMessages(Constants.MSG_CAMERA_OPEN); + } + mHandler.sendEmptyMessageDelayed(Constants.MSG_CAMERA_CLOSED, + Constants.CAMERA_EVENT_DELAY_TIME); + } + } + + @Override + public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) { + super.onCameraOpened(cameraId, packageId); + if (cameraId.equals(Constants.FRONT_CAMERA_ID)) { + mOpenEvent = SystemClock.elapsedRealtime(); + if (SystemClock.elapsedRealtime() - mClosedEvent < + Constants.CAMERA_EVENT_DELAY_TIME && + mHandler.hasMessages(Constants.MSG_CAMERA_CLOSED)) { + mHandler.removeMessages(Constants.MSG_CAMERA_CLOSED); + } + mHandler.sendEmptyMessageDelayed(Constants.MSG_CAMERA_OPEN, + Constants.CAMERA_EVENT_DELAY_TIME); + } + } + }; + + private SensorEventListener mFreeFallListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + if (event.values[0] == 2.0f) { + updateMotor(Constants.CLOSE_CAMERA_STATE); + goBackHome(); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + }; + + // Service + @Override + public void onCreate() { + CameraManager cameraManager = getSystemService(CameraManager.class); + cameraManager.registerAvailabilityCallback(mAvailabilityCallback, null); + + mSensorManager = getSystemService(SensorManager.class); + mFreeFallSensor = SensorsUtils.getSensor(mSensorManager, "xiaomi.sensor.free_fall"); + mProximitySensor = new ProximitySensor(this); + + mPopupCameraPreferences = new PopupCameraPreferences(this); + + mSoundPool = new SoundPool.Builder() + .setMaxStreams(1) + .setAudioAttributes(new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) + .build()) + .build(); + String[] soundNames = getResources().getStringArray(R.array.popupcamera_effects_names); + mSounds = new int[soundNames.length]; + for (int i = 0; i < soundNames.length; i++) { + mSounds[i] = mSoundPool.load(Constants.POPUP_SOUND_PATH + soundNames[i], 1); + } + + IMotor motor = getMotor(); + if (motor == null) { + return; + } + + try { + int status = motor.getMotorStatus(); + if (status == Constants.MOTOR_STATUS_POPUP_OK || + status == Constants.MOTOR_STATUS_POPUP_JAMMED || + status == Constants.MOTOR_STATUS_TAKEBACK_JAMMED) { + mHandler.sendEmptyMessage(Constants.MSG_CAMERA_CLOSED); + } + } catch (RemoteException e) { + // Do nothing + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (DEBUG) + Log.d(TAG, "Starting service"); + IMotor motor = getMotor(); + if (motor == null) { + return START_STICKY; + } + mProximitySensor.enable(); + return START_STICKY; + } + + @Override + public void onDestroy() { + if (DEBUG) + Log.d(TAG, "Destroying service"); + IMotor motor = getMotor(); + if (motor == null) { + return; + } + mProximitySensor.disable(); + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + // Handler.Callback + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case Constants.MSG_CAMERA_CLOSED: { + updateMotor(Constants.CLOSE_CAMERA_STATE); + } + break; + case Constants.MSG_CAMERA_OPEN: { + updateMotor(Constants.OPEN_CAMERA_STATE); + } + break; + } + return true; + } + + static public IMotor getMotorService() { + IMotor motor = null; + try { + motor = IMotor.getService(); + } catch (NoSuchElementException | RemoteException e) { + // Do nothing + } + return motor; + } + + public IMotor getMotor() { + if (mMotor == null) { + try { + mMotor = getMotorService(); + if (mMotor != null) { + mMotor.setMotorCallback(mMotorCallback); + mMotor.asBinder().linkToDeath((cookie) -> { + mMotor = null; + }, 0); + } + } catch (RemoteException e) { + // Do nothing + } + } + return mMotor; + } + + private void updateMotor(String cameraState) { + IMotor motor = getMotor(); + if (motor == null || mProximitySensor.getSawNear()) { + return; + } + final Runnable r = () -> { + mMotorBusy = true; + try { + int status = motor.getMotorStatus(); + if (DEBUG) + Log.d(TAG, "updateMotor: status=" + status); + if (cameraState.equals(Constants.OPEN_CAMERA_STATE) && + motor.getMotorStatus() == Constants.MOTOR_STATUS_TAKEBACK_OK) { + lightUp(); + playSoundEffect(Constants.OPEN_CAMERA_STATE); + motor.popupMotor(1); + mSensorManager.registerListener(mFreeFallListener, mFreeFallSensor, + SensorManager.SENSOR_DELAY_NORMAL); + } else if (cameraState.equals(Constants.CLOSE_CAMERA_STATE) && + motor.getMotorStatus() == Constants.MOTOR_STATUS_POPUP_OK) { + lightUp(); + playSoundEffect(Constants.CLOSE_CAMERA_STATE); + motor.takebackMotor(1); + mSensorManager.unregisterListener(mFreeFallListener, mFreeFallSensor); + } else { + mMotorBusy = false; + if (status == Constants.MOTOR_STATUS_REQUEST_CALIB || + status == Constants.MOTOR_STATUS_POPUP_JAMMED || + status == Constants.MOTOR_STATUS_TAKEBACK_JAMMED || + status == Constants.MOTOR_STATUS_CALIB_ERROR) { + showErrorDialog(); + } + return; + } + } catch (RemoteException e) { + // Do nothing + } + mHandler.postDelayed(() -> mMotorBusy = false, 1200); + }; + + if (mMotorBusy) { + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (mMotorBusy) { + mHandler.postDelayed(this, 100); + } else { + mHandler.post(r); + } + } + }, 100); + } else { + mHandler.post(r); + } + } + + private void lightUp() { + if (mPopupCameraPreferences.isLedAllowed()) { + FileUtils.writeLine(Constants.RED_LED_PATH, "1"); + FileUtils.writeLine(Constants.GREEN_LED_PATH, "1"); + FileUtils.writeLine(Constants.BLUE_LED_PATH, "1"); + FileUtils.writeLine(Constants.RED_RIGHT_LED_PATH, "1"); + FileUtils.writeLine(Constants.GREEN_RIGHT_LED_PATH, "1"); + FileUtils.writeLine(Constants.BLUE_RIGHT_LED_PATH, "1"); + + mHandler.postDelayed(() -> { + FileUtils.writeLine(Constants.RED_LED_PATH, "0"); + FileUtils.writeLine(Constants.GREEN_LED_PATH, "0"); + FileUtils.writeLine(Constants.BLUE_LED_PATH, "0"); + FileUtils.writeLine(Constants.RED_RIGHT_LED_PATH, "0"); + FileUtils.writeLine(Constants.GREEN_RIGHT_LED_PATH, "0"); + FileUtils.writeLine(Constants.BLUE_RIGHT_LED_PATH, "0"); + }, 2300); + } + } + + private void calibrateMotor() { + synchronized (mLock) { + IMotor motor = getMotor(); + if (mMotorCalibrating || motor == null) + return; + try { + mMotorCalibrating = true; + motor.calibration(); + } catch (RemoteException e) { + // Do nothing + } + } + } + + private void showCalibrationResult(int status) { + mHandler.post(() -> { + Toast.makeText(PopupCameraService.this, + status == Constants.MOTOR_STATUS_CALIB_OK + ? R.string.popup_camera_calibrate_success + : R.string.popup_camera_calibrate_failed, + Toast.LENGTH_LONG) + .show(); + }); + } + + private void showErrorDialog() { + if (mErrorDialogShowing) { + return; + } + mErrorDialogShowing = true; + goBackHome(); + mHandler.post(() -> { + Resources res = getResources(); + String cameraState = "-1"; + int dialogMessageResId = cameraState.equals(Constants.CLOSE_CAMERA_STATE) + ? R.string.popup_camera_takeback_failed_times_calibrate + : R.string.popup_camera_popup_failed_times_calibrate; + AlertDialog alertDialog = new AlertDialog.Builder(this, R.style.SystemAlertDialogTheme) + .setTitle(res.getString(R.string.popup_camera_tip)) + .setMessage(res.getString(dialogMessageResId)) + .setPositiveButton( + res.getString(R.string.popup_camera_calibrate_now), + (dialog, which) -> { + calibrateMotor(); + }) + .setNegativeButton(res.getString(android.R.string.cancel), null) + .create(); + alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + alertDialog.setCanceledOnTouchOutside(false); + alertDialog.show(); + alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialogInterface) { + mErrorDialogShowing = false; + } + }); + }); + } + + private void playSoundEffect(String state) { + int soundEffect = Integer.parseInt(mPopupCameraPreferences.getSoundEffect()); + if (soundEffect != -1) { + if (state.equals(Constants.CLOSE_CAMERA_STATE)) { + soundEffect++; + } + mSoundPool.play(mSounds[soundEffect], 1.0f, 1.0f, 0, 0, 1.0f); + } + } + + public void goBackHome() { + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityAsUser(homeIntent, null, UserHandle.CURRENT); + } +} diff --git a/parts/src/org/lineageos/settings/popupcamera/PopupCameraSettingsActivity.java b/parts/src/org/lineageos/settings/popupcamera/PopupCameraSettingsActivity.java new file mode 100644 index 0000000..e7a075b --- /dev/null +++ b/parts/src/org/lineageos/settings/popupcamera/PopupCameraSettingsActivity.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015-2016 The CyanogenMod Project + * 2017 The LineageOS Project + * + * 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 org.lineageos.settings.popupcamera; + +import android.os.Bundle; +import android.preference.PreferenceActivity; + +public class PopupCameraSettingsActivity extends PreferenceActivity { + + private static final String TAG_POPUPCAMERA = "popupcamera"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getFragmentManager() + .beginTransaction() + .replace(android.R.id.content, new PopupCameraSettingsFragment(), + TAG_POPUPCAMERA) + .commit(); + } +} \ No newline at end of file diff --git a/parts/src/org/lineageos/settings/popupcamera/PopupCameraSettingsFragment.java b/parts/src/org/lineageos/settings/popupcamera/PopupCameraSettingsFragment.java new file mode 100644 index 0000000..d6eb7ec --- /dev/null +++ b/parts/src/org/lineageos/settings/popupcamera/PopupCameraSettingsFragment.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 The LineageOS Project + * + * 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 org.lineageos.settings.popupcamera; + +import android.os.Bundle; +import android.view.MenuItem; + +import androidx.preference.Preference; +import androidx.preference.Preference.OnPreferenceChangeListener; +import androidx.preference.PreferenceFragment; + +import org.lineageos.settings.R; + +public class PopupCameraSettingsFragment + extends PreferenceFragment implements OnPreferenceChangeListener { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.popup_settings); + getActivity().getActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + return false; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + getActivity().onBackPressed(); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/parts/src/org/lineageos/settings/popupcamera/PopupCameraUtils.java b/parts/src/org/lineageos/settings/popupcamera/PopupCameraUtils.java new file mode 100644 index 0000000..5a4d859 --- /dev/null +++ b/parts/src/org/lineageos/settings/popupcamera/PopupCameraUtils.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The LineageOS Project + * + * 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 org.lineageos.settings.popupcamera; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.util.Log; + +public class PopupCameraUtils { + + private static final String TAG = "PopupCameraUtils"; + private static final boolean DEBUG = false; + + private static final boolean isPopUpMotorAvailable() { + return PopupCameraService.getMotorService() != null; + } + + protected static void startService(Context context) { + if (DEBUG) Log.d(TAG, "Starting service"); + context.startServiceAsUser(new Intent(context, PopupCameraService.class), + UserHandle.CURRENT); + PackageManager pm = context.getPackageManager(); + pm.setComponentEnabledSetting( + new ComponentName(context, PopupCameraSettingsActivity.class), + pm.COMPONENT_ENABLED_STATE_ENABLED, pm.SYNCHRONOUS); + } + + protected static void stopService(Context context) { + if (DEBUG) Log.d(TAG, "Stopping service"); + context.stopServiceAsUser(new Intent(context, PopupCameraService.class), + UserHandle.CURRENT); + PackageManager pm = context.getPackageManager(); + pm.setComponentEnabledSetting( + new ComponentName(context, PopupCameraSettingsActivity.class), + pm.COMPONENT_ENABLED_STATE_DEFAULT, pm.SYNCHRONOUS); + } + + public static void checkPopupCameraService(Context context) { + if (isPopUpMotorAvailable()) { + startService(context); + } else { + stopService(context); + } + } +} diff --git a/parts/src/org/lineageos/settings/sensors/PickupSensor.java b/parts/src/org/lineageos/settings/sensors/PickupSensor.java new file mode 100644 index 0000000..4d9cd61 --- /dev/null +++ b/parts/src/org/lineageos/settings/sensors/PickupSensor.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2017-2020 The LineageOS Project + * + * 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 org.lineageos.settings.sensors; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.SystemClock; +import android.util.Log; + +import org.lineageos.settings.doze.DozeUtils; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class PickupSensor implements SensorEventListener { + + private static final boolean DEBUG = false; + private static final String TAG = "PickupSensor"; + + private static final int MIN_PULSE_INTERVAL_MS = 2500; + + private SensorManager mSensorManager; + private Sensor mSensor; + private Context mContext; + private ExecutorService mExecutorService; + + private long mEntryTimestamp; + + public PickupSensor(Context context) { + mContext = context; + mSensorManager = mContext.getSystemService(SensorManager.class); + mSensor = SensorsUtils.getSensor(mSensorManager, "xiaomi.sensor.pickup"); + mExecutorService = Executors.newSingleThreadExecutor(); + } + + private Future submit(Runnable runnable) { + return mExecutorService.submit(runnable); + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (DEBUG) Log.d(TAG, "Got sensor event: " + event.values[0]); + + long delta = SystemClock.elapsedRealtime() - mEntryTimestamp; + if (delta < MIN_PULSE_INTERVAL_MS) { + return; + } + + mEntryTimestamp = SystemClock.elapsedRealtime(); + + if (event.values[0] == 1) { + DozeUtils.launchDozePulse(mContext); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + /* Empty */ + } + + public void enable() { + if (DEBUG) Log.d(TAG, "Enabling"); + submit(() -> { + mSensorManager.registerListener(this, mSensor, + SensorManager.SENSOR_DELAY_NORMAL); + mEntryTimestamp = SystemClock.elapsedRealtime(); + }); + } + + public void disable() { + if (DEBUG) Log.d(TAG, "Disabling"); + submit(() -> { + mSensorManager.unregisterListener(this, mSensor); + }); + } +} diff --git a/parts/src/org/lineageos/settings/sensors/ProximitySensor.java b/parts/src/org/lineageos/settings/sensors/ProximitySensor.java new file mode 100644 index 0000000..4be81fa --- /dev/null +++ b/parts/src/org/lineageos/settings/sensors/ProximitySensor.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2017-2018 The LineageOS Project + * + * 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 org.lineageos.settings.sensors; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.util.Log; + +import org.lineageos.settings.doze.DozeUtils; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class ProximitySensor implements SensorEventListener { + + private static final boolean DEBUG = false; + private static final String TAG = "ProximitySensor"; + + // Maximum time for the hand to cover the sensor: 1s + private static final int HANDWAVE_MAX_DELTA_NS = 1000 * 1000 * 1000; + + // Minimum time until the device is considered to have been in the pocket: 2s + private static final int POCKET_MIN_DELTA_NS = 2000 * 1000 * 1000; + private boolean mSawNear = false; + private SensorManager mSensorManager; + private Sensor mSensor; + private Context mContext; + private ExecutorService mExecutorService; + private long mInPocketTime = 0; + + public ProximitySensor(Context context) { + mContext = context; + mSensorManager = mContext.getSystemService(SensorManager.class); + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY, false); + mExecutorService = Executors.newSingleThreadExecutor(); + } + + private Future submit(Runnable runnable) { + return mExecutorService.submit(runnable); + } + + @Override + public void onSensorChanged(SensorEvent event) { + boolean isNear = event.values[0] < mSensor.getMaximumRange(); + if (mSawNear && !isNear) { + if (shouldPulse(event.timestamp)) { + DozeUtils.launchDozePulse(mContext); + } + } else { + mInPocketTime = event.timestamp; + } + mSawNear = isNear; + } + + private boolean shouldPulse(long timestamp) { + long delta = timestamp - mInPocketTime; + + if (DozeUtils.isHandwaveGestureEnabled(mContext) && + DozeUtils.isPocketGestureEnabled(mContext)) { + return true; + } else if (DozeUtils.isHandwaveGestureEnabled(mContext)) { + return delta < HANDWAVE_MAX_DELTA_NS; + } else if (DozeUtils.isPocketGestureEnabled(mContext)) { + return delta >= POCKET_MIN_DELTA_NS; + } + return false; + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + /* Empty */ + } + + public void enable() { + if (DEBUG) Log.d(TAG, "Enabling"); + submit(() -> { + mSensorManager.registerListener(this, mSensor, + SensorManager.SENSOR_DELAY_NORMAL); + }); + } + + public void disable() { + if (DEBUG) Log.d(TAG, "Disabling"); + submit(() -> { + mSensorManager.unregisterListener(this, mSensor); + }); + } + + public boolean getSawNear() { + return mSawNear; + } +} diff --git a/parts/src/org/lineageos/settings/sensors/SensorsUtils.java b/parts/src/org/lineageos/settings/sensors/SensorsUtils.java new file mode 100644 index 0000000..eac94bb --- /dev/null +++ b/parts/src/org/lineageos/settings/sensors/SensorsUtils.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2020 The LineageOS Project + * + * 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 org.lineageos.settings.sensors; + +import android.hardware.Sensor; +import android.hardware.SensorManager; + +public final class SensorsUtils { + public static Sensor getSensor(SensorManager sm, String type) { + for (Sensor sensor : sm.getSensorList(Sensor.TYPE_ALL)) { + if (type.equals(sensor.getStringType())) { + return sensor; + } + } + return null; + } +} diff --git a/parts/src/org/lineageos/settings/utils/FileUtils.java b/parts/src/org/lineageos/settings/utils/FileUtils.java new file mode 100644 index 0000000..2228ff8 --- /dev/null +++ b/parts/src/org/lineageos/settings/utils/FileUtils.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * + * 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 org.lineageos.settings.utils; + +import android.util.Log; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +public final class FileUtils { + private static final String TAG = "FileUtils"; + + private FileUtils() { + // This class is not supposed to be instantiated + } + + /** + * Reads the first line of text from the given file. + * Reference {@link BufferedReader#readLine()} for clarification on what a + * line is + * + * @return the read line contents, or null on failure + */ + public static String readOneLine(String fileName) { + String line = null; + BufferedReader reader = null; + + try { + reader = new BufferedReader(new FileReader(fileName), 512); + line = reader.readLine(); + } catch (FileNotFoundException e) { + Log.w(TAG, "No such file " + fileName + " for reading", e); + } catch (IOException e) { + Log.e(TAG, "Could not read from file " + fileName, e); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (IOException e) { + // Ignored, not much we can do anyway + } + } + + return line; + } + + /** + * Writes the given value into the given file + * + * @return true on success, false on failure + */ + public static boolean writeLine(String fileName, String value) { + BufferedWriter writer = null; + + try { + writer = new BufferedWriter(new FileWriter(fileName)); + writer.write(value); + } catch (FileNotFoundException e) { + Log.w(TAG, "No such file " + fileName + " for writing", e); + return false; + } catch (IOException e) { + Log.e(TAG, "Could not write to file " + fileName, e); + return false; + } finally { + try { + if (writer != null) { + writer.close(); + } + } catch (IOException e) { + // Ignored, not much we can do anyway + } + } + + return true; + } + + /** + * Checks whether the given file exists + * + * @return true if exists, false if not + */ + public static boolean fileExists(String fileName) { + final File file = new File(fileName); + return file.exists(); + } + + /** + * Checks whether the given file is readable + * + * @return true if readable, false if not + */ + public static boolean isFileReadable(String fileName) { + final File file = new File(fileName); + return file.exists() && file.canRead(); + } + + /** + * Checks whether the given file is writable + * + * @return true if writable, false if not + */ + public static boolean isFileWritable(String fileName) { + final File file = new File(fileName); + return file.exists() && file.canWrite(); + } + + /** + * Deletes an existing file + * + * @return true if the delete was successful, false if not + */ + public static boolean delete(String fileName) { + final File file = new File(fileName); + boolean ok = false; + try { + ok = file.delete(); + } catch (SecurityException e) { + Log.w(TAG, "SecurityException trying to delete " + fileName, e); + } + return ok; + } + + /** + * Renames an existing file + * + * @return true if the rename was successful, false if not + */ + public static boolean rename(String srcPath, String dstPath) { + final File srcFile = new File(srcPath); + final File dstFile = new File(dstPath); + boolean ok = false; + try { + ok = srcFile.renameTo(dstFile); + } catch (SecurityException e) { + Log.w(TAG, + "SecurityException trying to rename " + srcPath + " to " + dstPath, + e); + } catch (NullPointerException e) { + Log.e(TAG, + "NullPointerException trying to rename " + srcPath + " to " + + dstPath, + e); + } + return ok; + } +}