<|start_filename|>src/org/protonaosp/elmyra/actions/ScreenshotAction.kt<|end_filename|>
/*
 * Copyright (C) 2020 The Proton AOSP 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.protonaosp.elmyra.actions

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.view.WindowManager

import com.android.internal.util.ScreenshotHelper

class ScreenshotAction(context: Context) : Action(context) {
    val helper = ScreenshotHelper(context)
    private val handler = Handler(Looper.getMainLooper())
    val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager

    override fun canRun() = pm.isInteractive()
    override fun canRunWhenScreenOff() = false

    override fun run() {
        helper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
                true, true, WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
                handler, null)
    }
}


<|start_filename|>src/org/protonaosp/elmyra/settings/SummaryProvider.kt<|end_filename|>
/*
 * Copyright (C) 2020 The Proton AOSP 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.protonaosp.elmyra.settings

import android.content.ContentProvider
import android.content.ContentValues
import android.content.SharedPreferences
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY

import org.protonaosp.elmyra.R
import org.protonaosp.elmyra.getDePrefs
import org.protonaosp.elmyra.getEnabled
import org.protonaosp.elmyra.getActionName

class SummaryProvider : ContentProvider() {
    private lateinit var prefs: SharedPreferences

    override fun onCreate(): Boolean {
        prefs = context.getDePrefs()
        return true
    }

    override fun call(method: String, uri: String, extras: Bundle?): Bundle {
        val bundle = Bundle()
        val summary = when (method) {
            "entry" -> if (prefs.getEnabled(context)) {
                context.getString(R.string.settings_entry_summary_on, prefs.getActionName(context))
            } else {
                context.getString(R.string.settings_entry_summary_off)
            }
            else -> throw IllegalArgumentException("Unknown method: $method")
        }

        bundle.putString(META_DATA_PREFERENCE_SUMMARY, summary)
        return bundle
    }

    override fun query(uri: Uri, projection: Array<String>, selection: String,
            selectionArgs: Array<String>, sortOrder: String): Cursor {
        throw UnsupportedOperationException()
    }

    override fun getType(uri: Uri): String {
        throw UnsupportedOperationException()
    }

    override fun insert(uri: Uri, values: ContentValues): Uri {
        throw UnsupportedOperationException()
    }

    override fun delete(uri: Uri, selection: String, selectionArgs: Array<String>): Int {
        throw UnsupportedOperationException()
    }

    override fun update(uri: Uri, values: ContentValues, selection: String, selectionArgs: Array<String>): Int {
        throw UnsupportedOperationException()
    }
}


<|start_filename|>src/org/protonaosp/elmyra/settings/ToggleTileService.kt<|end_filename|>
/*
 * Copyright (C) 2020 The Proton AOSP 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.protonaosp.elmyra.settings

import android.content.Context
import android.content.SharedPreferences
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.preference.PreferenceManager

import org.protonaosp.elmyra.R
import org.protonaosp.elmyra.getDePrefs
import org.protonaosp.elmyra.getEnabled

class ToggleTileService : TileService(), SharedPreferences.OnSharedPreferenceChangeListener {
    private lateinit var prefs: SharedPreferences

    override fun onStartListening() {
        prefs = getDePrefs()
        prefs.registerOnSharedPreferenceChangeListener(this)
        update()
    }

    override fun onStopListening() {
        prefs.unregisterOnSharedPreferenceChangeListener(this)
    }

    private fun update(state: Boolean? = null) {
        qsTile.state = if (state ?: prefs.getEnabled(this)) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
        qsTile.updateTile()
    }

    override fun onClick() {
        val newState = !prefs.getEnabled(this)
        prefs.edit().putBoolean(getString(R.string.pref_key_enabled), newState).commit()
        update(newState)
    }

    override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
        if (key == getString(R.string.pref_key_enabled)) {
            update()
        }
    }
}


<|start_filename|>src/org/protonaosp/elmyra/settings/SettingsFragment.kt<|end_filename|>
/*
 * Copyright (C) 2020 The Proton AOSP 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.protonaosp.elmyra.settings

import android.os.Bundle
import android.content.SharedPreferences
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.ListPreference
import androidx.preference.SwitchPreference
import com.android.settings.widget.LabeledSeekBarPreference
import com.android.settings.widget.SeekBarPreference

import org.protonaosp.elmyra.R
import org.protonaosp.elmyra.getDePrefs
import org.protonaosp.elmyra.PREFS_NAME
import org.protonaosp.elmyra.getEnabled
import org.protonaosp.elmyra.getSensitivity
import org.protonaosp.elmyra.getAction
import org.protonaosp.elmyra.getActionName
import org.protonaosp.elmyra.getAllowScreenOff

class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
    private lateinit var prefs: SharedPreferences

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.settings, rootKey)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        preferenceManager.setStorageDeviceProtected()
        preferenceManager.sharedPreferencesName = PREFS_NAME

        prefs = context!!.getDePrefs()
        prefs.registerOnSharedPreferenceChangeListener(this)
        updateUi()
    }

    override fun onDestroy() {
        super.onDestroy()
        prefs.unregisterOnSharedPreferenceChangeListener(this)
    }

    override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
        updateUi()
    }

    private fun updateUi() {
        // Enabled
        findPreference<SwitchPreference>(getString(R.string.pref_key_enabled))?.apply {
            setChecked(prefs.getEnabled(context))
        }

        // Sensitivity value
        findPreference<LabeledSeekBarPreference>(getString(R.string.pref_key_sensitivity))?.apply {
            progress = prefs.getSensitivity(context)
            setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_TICKS)
        }

        // Action value and summary
        findPreference<ListPreference>(getString(R.string.pref_key_action))?.apply {
            value = prefs.getAction(context)
            summary = prefs.getActionName(context)
        }

        // Screen state based on action
        findPreference<SwitchPreference>(getString(R.string.pref_key_allow_screen_off))?.apply {
            val screenForced = prefs.getBoolean(getString(R.string.pref_key_allow_screen_off_action_forced), false)
            setEnabled(!screenForced)
            if (screenForced) {
                setSummary(getString(R.string.setting_screen_off_blocked_summary))
                setPersistent(false)
                setChecked(false)
            } else {
                setSummary(getString(R.string.setting_screen_off_summary))
                setPersistent(true)
                setChecked(prefs.getAllowScreenOff(context))
            }
        }
    }
}


<|start_filename|>src/org/protonaosp/elmyra/actions/FlashlightAction.kt<|end_filename|>
/*
 * Copyright (C) 2020 The Proton AOSP 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.protonaosp.elmyra.actions

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.util.Log

import org.protonaosp.elmyra.TAG

class FlashlightAction(context: Context) : Action(context) {
    private val handler = Handler(Looper.getMainLooper())
    private val cm = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
    private val torchCamId = findCamera()
    private var available = true
    private var enabled = false

    private val torchCallback = object : CameraManager.TorchCallback() {
        override fun onTorchModeUnavailable(cameraId: String) {
            if (cameraId == torchCamId) {
                available = false
            }
        }

        override fun onTorchModeChanged(cameraId: String, newEnabled: Boolean) {
            if (cameraId == torchCamId) {
                available = true
                enabled = newEnabled
            }
        }
    }

    init {
        if (torchCamId != null) {
            cm.registerTorchCallback(torchCallback, handler)
        }
    }

    override fun canRun() = torchCamId != null && available

    override fun run() {
        try {
            cm.setTorchMode(torchCamId, !enabled)
        } catch (e: CameraAccessException) {
            Log.e(TAG, "Failed to set torch mode to $enabled", e)
            return
        }

        enabled = !enabled
    }

    override fun destroy() {
        if (torchCamId != null) {
            cm.unregisterTorchCallback(torchCallback)
        }
    }

    private fun findCamera(): String? {
        for (id in cm.cameraIdList) {
            val characteristics = cm.getCameraCharacteristics(id)
            val flashAvailable = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
            val lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)

            if (flashAvailable != null && flashAvailable && lensFacing != null &&
                    lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
                return id
            }
        }

        return null
    }
}


<|start_filename|>src/org/protonaosp/elmyra/Preferences.kt<|end_filename|>
/*
 * Copyright (C) 2020 The Proton AOSP 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.protonaosp.elmyra

import android.content.Context
import android.content.SharedPreferences

const val PREFS_NAME = "elmyra_preferences"

fun Context.getDePrefs(): SharedPreferences {
    return createDeviceProtectedStorageContext()
            .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
}

fun SharedPreferences.getEnabled(context: Context): Boolean {
    return getBoolean(context.getString(R.string.pref_key_enabled),
            context.resources.getBoolean(R.bool.default_enabled))
}

fun SharedPreferences.getAction(context: Context): String {
    return getString(context.getString(R.string.pref_key_action),
            context.getString(R.string.default_action))
}

fun SharedPreferences.getAllowScreenOff(context: Context): Boolean {
    return getBoolean(context.getString(R.string.pref_key_allow_screen_off),
            context.resources.getBoolean(R.bool.default_allow_screen_off))
}

fun SharedPreferences.getSensitivity(context: Context): Int {
    return getInt(context.getString(R.string.pref_key_sensitivity),
            context.resources.getInteger(R.integer.default_sensitivity))
}

fun SharedPreferences.getActionName(context: Context): String {
    val actionNames = context.resources.getStringArray(R.array.action_names)
    val actionValues = context.resources.getStringArray(R.array.action_values)
    return actionNames[actionValues.indexOf(getAction(context))]
}


<|start_filename|>src/com/android/settings/widget/DefaultIndicatorSeekBar.java<|end_filename|>
/*
 * Copyright (C) 2017 The Android Open Source 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 com.android.settings.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.SeekBar;

public class DefaultIndicatorSeekBar extends SeekBar {

    private int mDefaultProgress = -1;

    public DefaultIndicatorSeekBar(Context context) {
        super(context);
    }

    public DefaultIndicatorSeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public DefaultIndicatorSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public DefaultIndicatorSeekBar(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * N.B. Only draws the default indicator tick mark, NOT equally spaced tick marks.
     */
    @Override
    protected void drawTickMarks(Canvas canvas) {
        if (isEnabled() && mDefaultProgress <= getMax() && mDefaultProgress >= getMin()) {
            final Drawable defaultIndicator = getTickMark();

            // Adjust the drawable's bounds to center it at the point where it's drawn.
            final int w = defaultIndicator.getIntrinsicWidth();
            final int h = defaultIndicator.getIntrinsicHeight();
            final int halfW = w >= 0 ? w / 2 : 1;
            final int halfH = h >= 0 ? h / 2 : 1;
            defaultIndicator.setBounds(-halfW, -halfH, halfW, halfH);

            // This mimics the computation of the thumb position, to get the true "default."
            final int availableWidth = getWidth() - mPaddingLeft - mPaddingRight;
            final int range = getMax() - getMin();
            final float scale = range > 0f ? mDefaultProgress / (float) range : 0f;
            final int offset = (int) ((scale * availableWidth) + 0.5f);
            final int indicatorPosition = isLayoutRtl() && getMirrorForRtl()
                    ? availableWidth - offset + mPaddingRight : offset + mPaddingLeft;

            final int saveCount = canvas.save();
            canvas.translate(indicatorPosition, getHeight() / 2);
            defaultIndicator.draw(canvas);
            canvas.restoreToCount(saveCount);
        }
    }

    /**
     * N.B. This sets the default *unadjusted* progress, i.e. in the SeekBar's [0 - max] terms.
     */
    public void setDefaultProgress(int defaultProgress) {
        if (mDefaultProgress != defaultProgress) {
            mDefaultProgress = defaultProgress;
            invalidate();
        }
    }

    public int getDefaultProgress() {
        return mDefaultProgress;
    }
}


<|start_filename|>src/org/protonaosp/elmyra/actions/CameraAction.kt<|end_filename|>
/*
 * Copyright (C) 2020 The Proton AOSP 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.protonaosp.elmyra.actions

import android.app.StatusBarManager
import android.content.Context
import android.content.Intent
import android.os.PowerManager
import android.os.SystemClock
import android.provider.MediaStore

class CameraAction(context: Context) : Action(context) {
    val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager

    override fun run() {
        if (!pm.isInteractive()) {
            pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                    "org.protonaosp.elmyra:GESTURE")
        }

        val intent = Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE).apply {
            addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or
                    Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        context.startActivity(intent)
    }
}


<|start_filename|>src/org/protonaosp/elmyra/settings/SearchProvider.kt<|end_filename|>
/*
 * Copyright (C) 2020 The Proton AOSP 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.protonaosp.elmyra.settings

import android.content.Intent
import android.database.Cursor
import android.database.MatrixCursor
import android.provider.SearchIndexablesProvider
import android.provider.SearchIndexablesContract.*

import org.protonaosp.elmyra.R

class SearchProvider : SearchIndexablesProvider() {
    override fun onCreate() = true
    override fun queryXmlResources(projection: Array<String>?) = MatrixCursor(INDEXABLES_XML_RES_COLUMNS)
    override fun queryNonIndexableKeys(projection: Array<String>?) = MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS)

    override fun queryRawData(projection: Array<String>?): Cursor {
        val ref = Array<Any?>(INDEXABLES_RAW_COLUMNS.size) { null }
        ref[COLUMN_INDEX_RAW_KEY] = context.getString(R.string.settings_entry_title)
        ref[COLUMN_INDEX_RAW_TITLE] = context.getString(R.string.settings_entry_title)
        ref[COLUMN_INDEX_RAW_SUMMARY_ON] = context.getString(R.string.setting_enabled_summary)
        ref[COLUMN_INDEX_RAW_KEYWORDS] = context.getString(R.string.settings_search_keywords)

        // For breadcrumb generation
        ref[COLUMN_INDEX_RAW_SCREEN_TITLE] = context.getString(R.string.settings_entry_title)
        ref[COLUMN_INDEX_RAW_CLASS_NAME] = SettingsActivity::class.java.name

        ref[COLUMN_INDEX_RAW_INTENT_ACTION] = Intent.ACTION_MAIN
        ref[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = context.applicationInfo.packageName
        ref[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = SettingsActivity::class.java.name

        return MatrixCursor(INDEXABLES_RAW_COLUMNS).apply {
            addRow(ref)
        }
    }
}
