Skip to content
This repository has been archived by the owner on Oct 18, 2021. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
biqqles committed Jan 14, 2018
0 parents commit bf695c3
Show file tree
Hide file tree
Showing 43 changed files with 1,514 additions and 0 deletions.
373 changes: 373 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Nextlit
[Jump to source](app/src/main/java/eu/biqqles/nextlit/)

**Nextlit** is a simple app that activates the [LP5523](http://www.ti.com/product/LP5523)-controlled segmented LEDs on the back of the [Nextbit Robin](https://en.wikipedia.org/wiki/Nextbit_Robin) and employs them as a fancy notification light. It's something I've seen quite a few people asking for, and after playing around with the kernel interface for a bit I realised it is pretty simple to utilise them for purposes beyond their rather limited use in the stock ROM.

Nextlit is very much a work in progress. Functionality is pretty basic at the moment, and it's currently more of a technology demonstrator than anything. The app allows you to preview the five patterns programmed into the LP5523 by Nextbit, and select one to activate when a notification is received.

If you want to use the app for any more than just previewing the available patterns, you'll need to give it notification access. The application does not read or care about the contents of notifications; the only thing is does is count them. The notification service is not persistent between reboots, so you'll need to manually re-enable it in the app if you reboot. Currently the service makes no attempt to reduce battery drain, but it doesn't seem too bad in my limited testing.

Make sure to turn off battery pulse in Settings if your ROM supports it and you want to see the LEDs while charging.

The app needs root in order to write to `/sys`. It'll work on all LOS and AOSP -based ROMs, and *should* work on stock, though I haven't tested it.

### To do
- minimise battery drain
- ‎custom patterns (I think I know how to do this, but needs testing)
- customisation: show only when screen off, ‎app blacklist, etc.
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
31 changes: 31 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 26
defaultConfig {
applicationId "eu.biqqles.nextlit"
minSdkVersion 23
targetSdkVersion 25
versionCode 1
versionName '1.0'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
}
}

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:design:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package eu.biqqles.nextlit;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();

assertEquals("eu.biqqles.nextlit", appContext.getPackageName());
}
}
31 changes: 31 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.biqqles.nextlit">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".NotificationLightsService"
android:label="@string/app_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>

</manifest>
90 changes: 90 additions & 0 deletions app/src/main/java/eu/biqqles/nextlit/LedControl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@

/*
* Copyright © 2018 biqqles.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package eu.biqqles.nextlit;

import android.util.Log;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.MessageFormat;


class LedControl {
// Various methods to control the LP5523 which drives the Robin's segmented LEDs, using sysfs.
private static final String PATTERN_FILE = "/sys/class/leds/lp5523:channel0/device/led_pattern";
private Process su = acquireRoot();

LedControl() throws IOException {
// Check we have access to /sys (and therefore indirectly that we have root access),
// otherwise throw an exception.
String result = execCommand("ls /sys\n", true);
if (result == null) {
throw new IOException();
}
}

void setPattern(int pattern) {
// Sets a predefined pattern programmed by Nextbit.
// For descriptions of the six patterns available (with 0 being clear), see arrays.xml.
clearPattern();
echoToFile(Integer.toString(pattern), PATTERN_FILE);
}

int getPattern() {
// Gets the currently set pattern.
String cmd = MessageFormat.format("cat {0}\n", PATTERN_FILE);
try {
return Integer.parseInt(execCommand(cmd, true));
} catch (NumberFormatException ex) {
return 0;
}
}

void clearPattern() {
// Clears current pattern and set all leds to LOW.
echoToFile("0", PATTERN_FILE);
}

private Process acquireRoot() {
// Create a Process with root privileges.
// It's worth noting that writing / reading from files with root privileges can only be
// done through shell commands, and not Java itself.
try {
return Runtime.getRuntime().exec("su");
} catch (IOException e) {
return null;
}
}

private void echoToFile(String data, String path) {
// Writes <data> to a file at <path>.
String cmd = MessageFormat.format("echo \"{0}\" > {1}\n", data, path);
execCommand(cmd, false);
}

private String execCommand(String command, boolean getOutput) {
// Executes a shell command, with root privileges, and optionally returns the result.
try {
DataOutputStream dos = new DataOutputStream(su.getOutputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(su.getInputStream()));
dos.writeBytes(command);
dos.flush();
if (getOutput) {
return br.readLine();
}
} catch (IOException ioe) {
Log.e("NEXTLIT", MessageFormat.format("Failed to execute {} with error {}",
command, ioe));
}
return null;
}
}
128 changes: 128 additions & 0 deletions app/src/main/java/eu/biqqles/nextlit/MainActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@

/*
* Copyright © 2018 biqqles.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package eu.biqqles.nextlit;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CompoundButton;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.Toast;
import android.widget.ToggleButton;

import java.io.IOException;


public class MainActivity extends AppCompatActivity {
LedControl ledcontrol;
SharedPreferences prefs;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

prefs = this.getSharedPreferences("nextlit", MODE_PRIVATE);

try {
ledcontrol = new LedControl();
} catch (IOException ioe) {
Toast.makeText(this, "App requires root access, closing", Toast.LENGTH_LONG).show();
finish();
}

final Spinner patternSpinner = findViewById(R.id.patternSpinner);
final ToggleButton previewButton = findViewById(R.id.previewButton);

patternSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView adapterView, View view, int i, long l) {
ledcontrol.clearPattern();
previewButton.setChecked(false);
// update preferences
// spinner index starts at 0 but patterns start at 1 (0 = clear)
prefs.edit().putInt("predef_pattern", i + 1).apply();
}

public void onNothingSelected(AdapterView<?> adapterView) {
}
});

patternSpinner.setSelection(prefs.getInt("predef_pattern", 1) - 1);

previewButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton button, boolean checked) {
if (checked) {
String patternName = patternSpinner.getSelectedItem().toString();
int patternNumber = patternSpinner.getSelectedItemPosition();

ledcontrol.clearPattern();
ledcontrol.setPattern(patternNumber + 1);

View view = findViewById(R.id.mainLayout);
Snackbar.make(view, "Previewing " + patternName, Snackbar.LENGTH_LONG).show();
} else {
ledcontrol.clearPattern();
}
}
});

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);

// add listener to start/stop service when switch toggled
final MenuItem item = menu.findItem(R.id.switch_item);
item.setActionView(R.layout.actionbar_switch_layout);
Switch serviceSwitch = item.getActionView().findViewById(R.id.serviceSwitch);

serviceSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton button, boolean checked) {
if (checked) {
// take user to Notification Access
String enabledNotificationListeners = Settings.Secure.getString(
getContentResolver(), "enabled_notification_listeners");
if (!enabledNotificationListeners.contains(getPackageName())) {
startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
Toast.makeText(MainActivity.this, "Enable Nextlit", Toast.LENGTH_SHORT).show();
}
startService(new Intent(MainActivity.this, NotificationListenerService.class));
Toast.makeText(MainActivity.this, "Service started", Toast.LENGTH_SHORT).show();
}
else {
ledcontrol.clearPattern();
stopService(new Intent(MainActivity.this, NotificationListenerService.class));
Toast.makeText(MainActivity.this, "Service stopped", Toast.LENGTH_SHORT).show();
}
}
});
return super.onCreateOptionsMenu(menu);
}
}





Loading

0 comments on commit bf695c3

Please sign in to comment.