--- /dev/null
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+/gen
--- /dev/null
+HeadphoneIndicator
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <resourceExtensions />
+ <wildcardResourcePatterns>
+ <entry name="!?*.java" />
+ <entry name="!?*.form" />
+ <entry name="!?*.class" />
+ <entry name="!?*.groovy" />
+ <entry name="!?*.scala" />
+ <entry name="!?*.flex" />
+ <entry name="!?*.kt" />
+ <entry name="!?*.clj" />
+ <entry name="!?*.aj" />
+ </wildcardResourcePatterns>
+ <annotationProcessing>
+ <profile default="true" name="Default" enabled="false">
+ <processorPath useClasspath="true" />
+ </profile>
+ </annotationProcessing>
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<component name="CopyrightManager">
+ <settings default="" />
+</component>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="GradleSettings">
+ <option name="linkedExternalProjectsSettings">
+ <GradleProjectSettings>
+ <option name="distributionType" value="LOCAL" />
+ <option name="externalProjectPath" value="$PROJECT_DIR$" />
+ <option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.8" />
+ <option name="gradleJvm" value="1.7" />
+ <option name="modules">
+ <set>
+ <option value="$PROJECT_DIR$" />
+ </set>
+ </option>
+ </GradleProjectSettings>
+ </option>
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="EntryPointsManager">
+ <entry_points version="2.0" />
+ </component>
+ <component name="NullableNotNullManager">
+ <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
+ <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
+ <option name="myNullables">
+ <value>
+ <list size="4">
+ <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
+ <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
+ <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
+ <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
+ </list>
+ </value>
+ </option>
+ <option name="myNotNulls">
+ <value>
+ <list size="4">
+ <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
+ <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
+ <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
+ <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
+ </list>
+ </value>
+ </option>
+ </component>
+ <component name="ProjectLevelVcsManager" settingsEditedManually="false">
+ <OptionsSetting value="true" id="Add" />
+ <OptionsSetting value="true" id="Remove" />
+ <OptionsSetting value="true" id="Checkout" />
+ <OptionsSetting value="true" id="Update" />
+ <OptionsSetting value="true" id="Status" />
+ <OptionsSetting value="true" id="Edit" />
+ <ConfirmationsSetting value="0" id="Add" />
+ <ConfirmationsSetting value="0" id="Remove" />
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/build/classes" />
+ </component>
+ <component name="ProjectType">
+ <option name="id" value="Android" />
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/headphoneindicator.iml" filepath="$PROJECT_DIR$/headphoneindicator.iml" />
+ </modules>
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="RunConfigurationProducerService">
+ <option name="ignoredProducers">
+ <set>
+ <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
+ <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
+ <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
+ </set>
+ </option>
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="" />
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="de.cweiske.headphoneindicator">
+
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/headphones_w"
+ android:label="@string/app_name"
+ android:supportsRtl="true">
+
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <service android:name=".BackgroundService"/>
+
+ <!-- auto-start after boot -->
+ <receiver android:name=".Autostart" >
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+</manifest>
--- /dev/null
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.5.0'
+ }
+}
+
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "23.0.2"
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src']
+ resources.srcDirs = ['src']
+ aidl.srcDirs = ['src']
+ renderscript.srcDirs = ['src']
+ res.srcDirs = ['res']
+ assets.srcDirs = ['res']
+ }
+ }
+ defaultConfig {
+ applicationId "de.cweiske.headphoneindicator"
+ minSdkVersion 19
+ targetSdkVersion 19
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
--- /dev/null
+<?xml version="1.0"?>
+<!-- Creator: CorelDRAW X5 -->
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:ns1="http://sozi.baierouge.fr"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xml:space="preserve"
+ style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
+ viewBox="0 0 7493 5700"
+ >
+ <defs
+ >
+ <style
+ type="text/css"
+ >
+
+ .fil0 {fill:black}
+
+ </style
+ >
+ </defs
+ >
+ <g
+ id="Layer_x0020_1"
+ >
+ <metadata
+ id="CorelCorpID_0Corel-Layer"
+ />
+ <path
+ class="fil0"
+ d="M1522 3175c77,15 152,37 224,66l0 2369c-146,58 -305,90 -471,90 -704,0 -1275,-571 -1275,-1275 0,-620 442,-1136 1028,-1251 0,-10 0,-18 0,-24 0,-861 300,-1644 784,-2213 493,-580 1177,-938 1935,-938 758,0 1441,359 1935,938 484,569 784,1351 784,2213 0,6 0,14 0,24 586,115 1028,631 1028,1251 0,704 -571,1275 -1275,1275 -166,0 -325,-32 -471,-90l0 -2369c72,-29 147,-51 224,-66l0 -24c0,-743 -254,-1412 -664,-1894 -401,-471 -953,-763 -1560,-763 -608,0 -1159,291 -1560,763 -410,482 -664,1151 -664,1894l0 24z"
+ />
+ </g
+ >
+<metadata
+ ><rdf:RDF
+ ><cc:Work
+ ><dc:format
+ >image/svg+xml</dc:format
+ ><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage"
+ /><cc:license
+ rdf:resource="http://creativecommons.org/licenses/publicdomain/"
+ /><dc:publisher
+ ><cc:Agent
+ rdf:about="http://openclipart.org/"
+ ><dc:title
+ >Openclipart</dc:title
+ ></cc:Agent
+ ></dc:publisher
+ ></cc:Work
+ ><cc:License
+ rdf:about="http://creativecommons.org/licenses/publicdomain/"
+ ><cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction"
+ /><cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution"
+ /><cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
+ /></cc:License
+ ></rdf:RDF
+ ></metadata
+ ></svg
+>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Creator: CorelDRAW X5 -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xml:space="preserve"
+ style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
+ viewBox="0 0 7493 5700"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ width="100%"
+ height="100%"
+ sodipodi:docname="headphones.svg"><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1440"
+ inkscape:window-height="826"
+ id="namedview14"
+ showgrid="false"
+ inkscape:zoom="0.041403509"
+ inkscape:cx="3794.8051"
+ inkscape:cy="2850"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" /><defs
+ id="defs4"><style
+ type="text/css"
+ id="style6">
+
+ .fil0 {fill:black}
+
+ </style></defs><g
+ id="Layer_x0020_1"
+ style="fill:#ffffff"><metadata
+ id="CorelCorpID_0Corel-Layer" /><path
+ class="fil0"
+ d="M1522 3175c77,15 152,37 224,66l0 2369c-146,58 -305,90 -471,90 -704,0 -1275,-571 -1275,-1275 0,-620 442,-1136 1028,-1251 0,-10 0,-18 0,-24 0,-861 300,-1644 784,-2213 493,-580 1177,-938 1935,-938 758,0 1441,359 1935,938 484,569 784,1351 784,2213 0,6 0,14 0,24 586,115 1028,631 1028,1251 0,704 -571,1275 -1275,1275 -166,0 -325,-32 -471,-90l0 -2369c72,-29 147,-51 224,-66l0 -24c0,-743 -254,-1412 -664,-1894 -401,-471 -953,-763 -1560,-763 -608,0 -1159,291 -1560,763 -410,482 -664,1151 -664,1894l0 24z"
+ id="path10"
+ style="fill:#ffffff" /></g><metadata
+ id="metadata12"><rdf:RDF><cc:Work><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><cc:license
+ rdf:resource="http://creativecommons.org/licenses/publicdomain/" /><dc:publisher><cc:Agent
+ rdf:about="http://openclipart.org/"><dc:title>Openclipart</dc:title></cc:Agent></dc:publisher><dc:title></dc:title></cc:Work><cc:License
+ rdf:about="http://creativecommons.org/licenses/publicdomain/"><cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /></cc:License></rdf:RDF></metadata></svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="16dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingTop="16dp"
+ tools:context="de.cweiske.headphoneindicator.MainActivity"
+ android:theme="@android:style/Theme">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/image"
+ android:layout_centerVertical="true"
+ android:layout_centerHorizontal="true" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/unplugged"
+ android:id="@+id/label"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true" />
+</RelativeLayout>
--- /dev/null
+<resources>
+ <string name="app_name">Headphone indicator</string>
+ <string name="plugged">Headphones are plugged in</string>
+ <string name="unplugged">Headphones are NOT plugged in</string>
+</resources>
--- /dev/null
+include ':'
--- /dev/null
+package de.cweiske.headphoneindicator;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Called when the phone finishes booting.
+ *
+ */
+public class Autostart extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context.startService(new Intent(context, BackgroundService.class));
+ }
+}
--- /dev/null
+package de.cweiske.headphoneindicator;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+
+/**
+ * Permanent service that runs even when the app itself is stopped
+ *
+ */
+public class BackgroundService extends Service
+{
+ protected NotificationReceiver notificationReceiver;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
+ this.notificationReceiver = new NotificationReceiver();
+ registerReceiver(this.notificationReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ unregisterReceiver(this.notificationReceiver);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return START_STICKY;
+ }
+}
--- /dev/null
+package de.cweiske.headphoneindicator;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Shows headphone icon and text, and starts the background service
+ *
+ */
+public class MainActivity extends Activity {
+ BroadcastReceiver headsetPlugReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+ if (extras.getInt("state") == 1) {
+ setPlugged(true);
+ } else {
+ setPlugged(false);
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ startService(new Intent(this, BackgroundService.class));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
+ registerReceiver(this.headsetPlugReceiver, filter);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ this.unregisterReceiver(this.headsetPlugReceiver);
+ }
+
+ public void setPlugged(boolean plugged)
+ {
+ ImageView view = (ImageView) findViewById(R.id.image);
+ TextView label = (TextView) findViewById(R.id.label);
+ if (plugged) {
+ view.setImageResource(R.drawable.headphones_w);
+ label.setText(R.string.plugged);
+ } else {
+ view.setImageResource(0);
+ label.setText(R.string.unplugged);
+ }
+ }
+}
--- /dev/null
+package de.cweiske.headphoneindicator;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Shows and hides the status bar notification.
+ *
+ */
+public class NotificationReceiver extends BroadcastReceiver {
+ static int NOT_ID = 1;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+
+ NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (extras.getInt("state") == 1) {
+ //plugged
+ nm.notify(
+ NOT_ID,
+ new Notification.Builder(context)
+ .setSmallIcon(R.drawable.headphones_w)
+ .setContentTitle(context.getResources().getString(R.string.plugged))
+ .setContentText("")
+ .build()
+ );
+ } else {
+ //unplugged
+ nm.cancel(NOT_ID);
+ }
+ }
+}