--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="gen">
+ <attributes>
+ <attribute name="ignore_optional_problems" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="src" path="lib">
+ <attributes>
+ <attribute name="ignore_optional_problems" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+ <classpathentry kind="output" path="bin/classes"/>
+</classpath>
--- /dev/null
+bin/
+gen/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<pmd>
+ <useProjectRuleSet>false</useProjectRuleSet>
+ <ruleSetFile>.ruleset</ruleSetFile>
+ <rules>
+ <rule>
+ <name>LooseCoupling</name>
+ <ruleset>Type Resolution Rules</ruleset>
+ </rule>
+ <rule>
+ <name>CloneMethodMustImplementCloneable</name>
+ <ruleset>Type Resolution Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SignatureDeclareThrowsException</name>
+ <ruleset>Type Resolution Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseSingleton</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SimplifyBooleanReturns</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SimplifyBooleanExpressions</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidDeeplyNestedIfStmts</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidReassigningParameters</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SwitchDensity</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ConstructorCallsOverridableMethod</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AccessorClassGeneration</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>FinalFieldCouldBeStatic</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>CloseResource</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>NonStaticInitializer</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>DefaultLabelNotLastInSwitchStmt</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>NonCaseLabelInSwitchStatement</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>OptimizableToArrayCall</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>BadComparison</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EqualsNull</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ConfusingTernary</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>InstantiationToGetClass</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>IdempotentOperations</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SimpleDateFormatNeedsLocale</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ImmutableField</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseLocaleWithCaseConversions</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidProtectedFieldInFinalClass</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AssignmentToNonFinalStatic</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MissingStaticMethodInNonInstantiatableClass</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidSynchronizedAtMethodLevel</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MissingBreakInSwitch</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseNotifyAllInsteadOfNotify</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidInstanceofChecksInCatchClause</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AbstractClassWithoutAbstractMethod</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SimplifyConditional</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>CompareObjectsWithEquals</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>PositionLiteralsFirstInComparisons</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnnecessaryLocalBeforeReturn</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>NonThreadSafeSingleton</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UncommentedEmptyMethod</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UncommentedEmptyConstructor</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidConstantsInterface</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnsynchronizedStaticDateFormatter</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>PreserveStackTrace</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseCollectionIsEmpty</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ClassWithOnlyPrivateConstructorsShouldBeFinal</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyMethodInAbstractClassShouldBeAbstract</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SingularField</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ReturnEmptyArrayRatherThanNull</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AbstractClassWithoutAnyMethod</name>
+ <ruleset>Design Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidCatchingThrowable</name>
+ <ruleset>Strict Exception Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SignatureDeclareThrowsException</name>
+ <ruleset>Type Resolution Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ExceptionAsFlowControl</name>
+ <ruleset>Strict Exception Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidCatchingNPE</name>
+ <ruleset>Strict Exception Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidThrowingRawExceptionTypes</name>
+ <ruleset>Strict Exception Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidThrowingNullPointerException</name>
+ <ruleset>Strict Exception Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidRethrowingException</name>
+ <ruleset>Strict Exception Rules</ruleset>
+ </rule>
+ <rule>
+ <name>DoNotExtendJavaLangError</name>
+ <ruleset>Strict Exception Rules</ruleset>
+ </rule>
+ <rule>
+ <name>DoNotThrowExceptionInFinally</name>
+ <ruleset>Strict Exception Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidThrowingNewInstanceOfSameException</name>
+ <ruleset>Strict Exception Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnusedPrivateField</name>
+ <ruleset>Unused Code Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnusedLocalVariable</name>
+ <ruleset>Unused Code Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnusedPrivateMethod</name>
+ <ruleset>Unused Code Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnusedFormalParameter</name>
+ <ruleset>Unused Code Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MoreThanOneLogger</name>
+ <ruleset>Java Logging Rules</ruleset>
+ </rule>
+ <rule>
+ <name>LoggerIsNotStaticFinal</name>
+ <ruleset>Java Logging Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SystemPrintln</name>
+ <ruleset>Java Logging Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidPrintStackTrace</name>
+ <ruleset>Java Logging Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidDuplicateLiterals</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>StringInstantiation</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>StringToString</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>InefficientStringBuffering</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnnecessaryCaseChange</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseStringBufferLength</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AppendCharacterWithChar</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ConsecutiveLiteralAppends</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseIndexOfChar</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>InefficientEmptyStringCheck</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>InsufficientStringBufferDeclaration</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UselessStringValueOf</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>StringBufferInstantiationWithChar</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseEqualsToCompareStrings</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidStringBufferField</name>
+ <ruleset>String and StringBuffer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ReplaceVectorWithList</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ReplaceHashtableWithMap</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ReplaceEnumerationWithIterator</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidEnumAsIdentifier</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidAssertAsIdentifier</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>IntegerInstantiation</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ByteInstantiation</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ShortInstantiation</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>LongInstantiation</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JUnit4TestShouldUseBeforeAnnotation</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JUnit4TestShouldUseAfterAnnotation</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JUnit4TestShouldUseTestAnnotation</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JUnit4SuitesShouldUseSuiteAnnotation</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JUnitUseExpected</name>
+ <ruleset>Migration Rules</ruleset>
+ </rule>
+ <rule>
+ <name>LocalVariableCouldBeFinal</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MethodArgumentCouldBeFinal</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidInstantiatingObjectsInLoops</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseArrayListInsteadOfVector</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SimplifyStartsWith</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseStringBufferForStringAppends</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseArraysAsList</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidArrayLoops</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnnecessaryWrapperObjectCreation</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AddEmptyString</name>
+ <ruleset>Optimization Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyCatchBlock</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyIfStmt</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyWhileStmt</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyTryBlock</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyFinallyBlock</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptySwitchStatements</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JumbledIncrementer</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ForLoopShouldBeWhileLoop</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnnecessaryConversionTemporary</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>OverrideBothEqualsAndHashcode</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>DoubleCheckedLocking</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ReturnFromFinallyBlock</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptySynchronizedBlock</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnnecessaryReturn</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyStaticInitializer</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnconditionalIfStatement</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyStatementNotInLoop</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>BooleanInstantiation</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnnecessaryFinalModifier</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>CollapsibleIfStatements</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UselessOverridingMethod</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ClassCastExceptionWithToArray</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidDecimalLiteralsInBigDecimalConstructor</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UselessOperationOnImmutable</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MisplacedNullCheck</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnusedNullCheckInEquals</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidThreadGroup</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>BrokenNullCheck</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>BigIntegerInstantiation</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidUsingOctalValues</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidUsingHardCodedIP</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>CheckResultSet</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidMultipleUnaryOperators</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyInitializer</name>
+ <ruleset>Basic Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MethodReturnsInternalArray</name>
+ <ruleset>Security Code Guidelines</ruleset>
+ </rule>
+ <rule>
+ <name>ArrayIsStoredDirectly</name>
+ <ruleset>Security Code Guidelines</ruleset>
+ </rule>
+ <rule>
+ <name>CouplingBetweenObjects</name>
+ <ruleset>Coupling Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ExcessiveImports</name>
+ <ruleset>Coupling Rules</ruleset>
+ </rule>
+ <rule>
+ <name>LooseCoupling</name>
+ <ruleset>Type Resolution Rules</ruleset>
+ </rule>
+ <rule>
+ <name>DuplicateImports</name>
+ <ruleset>Import Statement Rules</ruleset>
+ </rule>
+ <rule>
+ <name>DontImportJavaLang</name>
+ <ruleset>Import Statement Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ImportFromSamePackage</name>
+ <ruleset>Import Statement Rules</ruleset>
+ </rule>
+ <rule>
+ <name>TooManyStaticImports</name>
+ <ruleset>Import Statement Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JUnitStaticSuite</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JUnitSpelling</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JUnitAssertionsShouldIncludeMessage</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>JUnitTestsShouldIncludeAssert</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>TestClassWithoutTestCases</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnnecessaryBooleanAssertion</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseAssertEqualsInsteadOfAssertTrue</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseAssertSameInsteadOfAssertTrue</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseAssertNullInsteadOfAssertTrue</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SimplifyBooleanAssertion</name>
+ <ruleset>JUnit Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnnecessaryConstructor</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>NullAssignment</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnusedModifier</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AssignmentInOperand</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AtLeastOneConstructor</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>DontImportSun</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SuspiciousOctalEscape</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>CallSuperInConstructor</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UnnecessaryParentheses</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>DefaultPackage</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>BooleanInversion</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidUsingShortType</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidUsingVolatile</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidUsingNativeCode</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidAccessibilityAlteration</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>DoNotCallGarbageCollectionExplicitly</name>
+ <ruleset>Controversial Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ShortMethodName</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>VariableNamingConventions</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MethodNamingConventions</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ClassNamingConventions</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AbstractNaming</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidDollarSigns</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MethodWithSameNameAsEnclosingClass</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SuspiciousHashcodeMethodName</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SuspiciousConstantFieldName</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>SuspiciousEqualsMethodName</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidFieldNameMatchingTypeName</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidFieldNameMatchingMethodName</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>NoPackage</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>PackageCase</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MisleadingVariableName</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>BooleanGetMethodName</name>
+ <ruleset>Naming Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ExcessiveMethodLength</name>
+ <ruleset>Code Size Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ExcessiveParameterList</name>
+ <ruleset>Code Size Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ExcessiveClassLength</name>
+ <ruleset>Code Size Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ExcessivePublicCount</name>
+ <ruleset>Code Size Rules</ruleset>
+ </rule>
+ <rule>
+ <name>NcssMethodCount</name>
+ <ruleset>Code Size Rules</ruleset>
+ </rule>
+ <rule>
+ <name>NcssTypeCount</name>
+ <ruleset>Code Size Rules</ruleset>
+ </rule>
+ <rule>
+ <name>NcssConstructorCount</name>
+ <ruleset>Code Size Rules</ruleset>
+ </rule>
+ <rule>
+ <name>EmptyFinalizer</name>
+ <ruleset>Finalizer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>FinalizeOnlyCallsSuperFinalize</name>
+ <ruleset>Finalizer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>FinalizeOverloaded</name>
+ <ruleset>Finalizer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>FinalizeDoesNotCallSuperFinalize</name>
+ <ruleset>Finalizer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>FinalizeShouldBeProtected</name>
+ <ruleset>Finalizer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>AvoidCallingFinalize</name>
+ <ruleset>Finalizer Rules</ruleset>
+ </rule>
+ <rule>
+ <name>UseCorrectExceptionLogging</name>
+ <ruleset>Jakarta Commons Logging Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ProperLogger</name>
+ <ruleset>Jakarta Commons Logging Rules</ruleset>
+ </rule>
+ <rule>
+ <name>MissingSerialVersionUID</name>
+ <ruleset>JavaBean Rules</ruleset>
+ </rule>
+ <rule>
+ <name>ProperCloneImplementation</name>
+ <ruleset>Clone Implementation Rules</ruleset>
+ </rule>
+ <rule>
+ <name>CloneThrowsCloneNotSupportedException</name>
+ <ruleset>Clone Implementation Rules</ruleset>
+ </rule>
+ <rule>
+ <name>CloneMethodMustImplementCloneable</name>
+ <ruleset>Type Resolution Rules</ruleset>
+ </rule>
+ </rules>
+ <includeDerivedFiles>false</includeDerivedFiles>
+ <violationsAsErrors>true</violationsAsErrors>
+</pmd>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>FonBot</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
--- /dev/null
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="ro.ieval.fonbot"
+ android:installLocation="internalOnly"
+ android:versionCode="1"
+ android:versionName="prerelease" >
+
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="17" />
+
+ <permission
+ android:name="ro.ieval.fonbot.permission.C2D_MESSAGE"
+ android:label="Receive GCM messages"
+ android:protectionLevel="signature" >
+ </permission>
+
+ <uses-permission android:name="ro.ieval.fonbot.permission.C2D_MESSAGE" />
+ <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.CALL_PHONE" />
+ <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.FLASHLIGHT" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_SMS" />
+ <uses-permission android:name="android.permission.REBOOT" />
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+ <permission
+ android:name="ro.ieval.fonbot.permission.SEND_NOTIFICATIONS"
+ android:description="@string/send_message_permission_desc"
+ android:label="send FonBot notifications" >
+ </permission>
+
+ <uses-feature
+ android:name="android.hardware.bluetooth"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera.autofocus"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera.flash"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera.front"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera.any"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.location"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.location.network"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.location.gps"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.telephony"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.wifi"
+ android:required="false" />
+
+ <application
+ android:name=".FonBotApplication"
+ android:allowBackup="true"
+ android:backupAgent="BackupAgent"
+ android:enabled="true"
+ android:hasCode="true"
+ android:label="@string/app_name"
+ android:persistent="false" >
+ <activity
+ android:name=".FonBotHelpActivity"
+ android:alwaysRetainTaskState="false"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:immersive="false"
+ android:label="@string/help_activity_label"
+ android:launchMode="standard"
+ android:multiprocess="false"
+ android:parentActivityName=".FonBotMainActivity"
+ android:stateNotNeeded="true"
+ android:windowSoftInputMode="stateAlwaysHidden" >
+ </activity>
+ <activity
+ android:name=".FonBotMainActivity"
+ android:immersive="false"
+ android:launchMode="standard"
+ android:multiprocess="false"
+ android:screenOrientation="unspecified"
+ android:stateNotNeeded="true"
+ android:uiOptions="splitActionBarWhenNarrow"
+ android:windowSoftInputMode="stateAlwaysHidden" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".FonBotLocalActivity"
+ android:exported="false" >
+ </activity>
+ <activity
+ android:name=".FonBotPreferenceActivity"
+ android:exported="false" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:scheme="ro.ieval.fonbot.FonBotPreferenceActivity" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".DialogActivity"
+ android:exported="false"
+ android:noHistory="true"
+ android:theme="@android:style/Theme.Dialog" >
+ </activity>
+
+ <service android:name=".GCMIntentService" >
+ </service>
+ <service android:name=".FonBotMainService" >
+ </service>
+
+ <receiver
+ android:name="com.google.android.gcm.GCMBroadcastReceiver"
+ android:permission="com.google.android.c2dm.permission.SEND" >
+ <intent-filter>
+ <action android:name="com.google.android.c2dm.intent.RECEIVE" />
+ <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
+
+ <category android:name="ro.ieval.fonbot" />
+ </intent-filter>
+ </receiver>
+ <receiver
+ android:name=".SmsReceiver"
+ android:permission="android.permission.BROADCAST_SMS" >
+ <intent-filter android:priority="2147483647" >
+ <action android:name="android.provider.Telephony.SMS_RECEIVED" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".SmsStatusReceiver" >
+ </receiver>
+ <receiver
+ android:name=".FonBotAdminReceiver"
+ android:exported="false"
+ android:permission="android.permission.BIND_DEVICE_ADMIN" >
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.app.device_admin"
+ android:resource="@xml/admin" />
+ </receiver>
+ <receiver
+ android:name=".ProtectedBroadcastReceiver"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.BATTERY_LOW" />
+ <action android:name="android.intent.action.BATTERY_OKAY" />
+ </intent-filter>
+ </receiver>
+ <receiver
+ android:name=".LocalBroadcastReceiver"
+ android:exported="false" >
+ <intent-filter>
+ <action android:name="ro.ieval.fonbot.LocalBroadcastReceiver.ACTION_POLL_ALARM" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+</manifest>
\ No newline at end of file
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
--- /dev/null
+/*
+ * Copyright (C) 2012 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 android.support.v4.app;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Build;
+import android.widget.RemoteViews;
+import java.util.ArrayList;
+
+/**
+ * Helper for accessing features in {@link android.app.Notification}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class NotificationCompat {
+ /**
+ * Obsolete flag indicating high-priority notifications; use the priority field instead.
+ *
+ * @deprecated Use {@link NotificationCompat.Builder#setPriority(int)} with a positive value.
+ */
+ @Deprecated
+ public static final int FLAG_HIGH_PRIORITY = 0x00000080;
+
+ /**
+ * Default notification priority for {@link NotificationCompat.Builder#setPriority(int)}.
+ * If your application does not prioritize its own notifications,
+ * use this value for all notifications.
+ */
+ public static final int PRIORITY_DEFAULT = 0;
+
+ /**
+ * Lower notification priority for {@link NotificationCompat.Builder#setPriority(int)},
+ * for items that are less important. The UI may choose to show
+ * these items smaller, or at a different position in the list,
+ * compared with your app's {@link #PRIORITY_DEFAULT} items.
+ */
+ public static final int PRIORITY_LOW = -1;
+
+ /**
+ * Lowest notification priority for {@link NotificationCompat.Builder#setPriority(int)};
+ * these items might not be shown to the user except under
+ * special circumstances, such as detailed notification logs.
+ */
+ public static final int PRIORITY_MIN = -2;
+
+ /**
+ * Higher notification priority for {@link NotificationCompat.Builder#setPriority(int)},
+ * for more important notifications or alerts. The UI may choose
+ * to show these items larger, or at a different position in
+ * notification lists, compared with your app's {@link #PRIORITY_DEFAULT} items.
+ */
+ public static final int PRIORITY_HIGH = 1;
+
+ /**
+ * Highest notification priority for {@link NotificationCompat.Builder#setPriority(int)},
+ * for your application's most important items that require the user's
+ * prompt attention or input.
+ */
+ public static final int PRIORITY_MAX = 2;
+
+ private static final NotificationCompatImpl IMPL;
+
+ interface NotificationCompatImpl {
+ public Notification build(Builder b);
+ }
+
+ static class NotificationCompatImplBase implements NotificationCompatImpl {
+ @SuppressWarnings("deprecation")
+ @Override
+ public Notification build(Builder b) {
+ Notification result = b.mNotification;
+ result.setLatestEventInfo(b.mContext, b.mContentTitle,
+ b.mContentText, b.mContentIntent);
+ // translate high priority requests into legacy flag
+ if (b.mPriority > PRIORITY_DEFAULT) {
+ result.flags |= FLAG_HIGH_PRIORITY;
+ }
+ return result;
+ }
+ }
+
+ static class NotificationCompatImplHoneycomb implements NotificationCompatImpl {
+ @Override
+ public Notification build(Builder b) {
+ return NotificationCompatHoneycomb.add(b.mContext, b.mNotification,
+ b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
+ b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon);
+ }
+ }
+
+ static class NotificationCompatImplIceCreamSandwich implements NotificationCompatImpl {
+ @Override
+ public Notification build(Builder b) {
+ return NotificationCompatIceCreamSandwich.add(b.mContext, b.mNotification,
+ b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
+ b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
+ b.mProgressMax, b.mProgress, b.mProgressIndeterminate);
+ }
+ }
+
+ static class NotificationCompatImplJellybean implements NotificationCompatImpl {
+ @Override
+ public Notification build(Builder b) {
+ NotificationCompatJellybean jbBuilder = new NotificationCompatJellybean(
+ b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+ b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
+ b.mProgressMax, b.mProgress, b.mProgressIndeterminate,
+ b.mUseChronometer, b.mPriority, b.mSubText);
+ for (Action action: b.mActions) {
+ jbBuilder.addAction(action.icon, action.title, action.actionIntent);
+ }
+ if (b.mStyle != null) {
+ if (b.mStyle instanceof BigTextStyle) {
+ BigTextStyle style = (BigTextStyle) b.mStyle;
+ jbBuilder.addBigTextStyle(style.mBigContentTitle,
+ style.mSummaryTextSet,
+ style.mSummaryText,
+ style.mBigText);
+ } else if (b.mStyle instanceof InboxStyle) {
+ InboxStyle style = (InboxStyle) b.mStyle;
+ jbBuilder.addInboxStyle(style.mBigContentTitle,
+ style.mSummaryTextSet,
+ style.mSummaryText,
+ style.mTexts);
+ } else if (b.mStyle instanceof BigPictureStyle) {
+ BigPictureStyle style = (BigPictureStyle) b.mStyle;
+ jbBuilder.addBigPictureStyle(style.mBigContentTitle,
+ style.mSummaryTextSet,
+ style.mSummaryText,
+ style.mPicture);
+ }
+ }
+ return(jbBuilder.build());
+ }
+ }
+
+ static {
+ if (Build.VERSION.SDK_INT >= 16) {
+ IMPL = new NotificationCompatImplJellybean();
+ } else if (Build.VERSION.SDK_INT >= 14) {
+ IMPL = new NotificationCompatImplIceCreamSandwich();
+ } else if (Build.VERSION.SDK_INT >= 11) {
+ IMPL = new NotificationCompatImplHoneycomb();
+ } else {
+ IMPL = new NotificationCompatImplBase();
+ }
+ }
+
+ /**
+ * Builder class for {@link NotificationCompat} objects. Allows easier control over
+ * all the flags, as well as help constructing the typical notification layouts.
+ * <p>
+ * On platform versions that don't offer expanded notifications, methods that depend on
+ * expanded notifications have no effect.
+ * </p>
+ * <p>
+ * For example, action buttons won't appear on platforms prior to Android 4.1. Action
+ * buttons depend on expanded notifications, which are only available in Android 4.1
+ * and later.
+ * <p>
+ * For this reason, you should always ensure that UI controls in a notification are also
+ * available in an {@link android.app.Activity} in your app, and you should always start that
+ * {@link android.app.Activity} when users click the notification. To do this, use the
+ * {@link NotificationCompat.Builder#setContentIntent setContentIntent()}
+ * method.
+ * </p>
+ *
+ */
+ public static class Builder {
+ Context mContext;
+
+ CharSequence mContentTitle;
+ CharSequence mContentText;
+ PendingIntent mContentIntent;
+ PendingIntent mFullScreenIntent;
+ RemoteViews mTickerView;
+ Bitmap mLargeIcon;
+ CharSequence mContentInfo;
+ int mNumber;
+ int mPriority;
+ boolean mUseChronometer;
+ Style mStyle;
+ CharSequence mSubText;
+ int mProgressMax;
+ int mProgress;
+ boolean mProgressIndeterminate;
+ ArrayList<Action> mActions = new ArrayList<Action>();
+
+ Notification mNotification = new Notification();
+
+ /**
+ * Constructor.
+ *
+ * Automatically sets the when field to {@link System#currentTimeMillis()
+ * System.currentTimeMillis()} and the audio stream to the
+ * {@link Notification#STREAM_DEFAULT}.
+ *
+ * @param context A {@link Context} that will be used to construct the
+ * RemoteViews. The Context will not be held past the lifetime of this
+ * Builder object.
+ */
+ public Builder(Context context) {
+ mContext = context;
+
+ // Set defaults to match the defaults of a Notification
+ mNotification.when = System.currentTimeMillis();
+ mNotification.audioStreamType = Notification.STREAM_DEFAULT;
+ mPriority = PRIORITY_DEFAULT;
+ }
+
+ /**
+ * Set the time that the event occurred. Notifications in the panel are
+ * sorted by this time.
+ */
+ public Builder setWhen(long when) {
+ mNotification.when = when;
+ return this;
+ }
+
+ /**
+ * Show the {@link Notification#when} field as a stopwatch.
+ *
+ * Instead of presenting <code>when</code> as a timestamp, the notification will show an
+ * automatically updating display of the minutes and seconds since <code>when</code>.
+ *
+ * Useful when showing an elapsed time (like an ongoing phone call).
+ *
+ * @see android.widget.Chronometer
+ * @see Notification#when
+ */
+ public Builder setUsesChronometer(boolean b) {
+ mUseChronometer = b;
+ return this;
+ }
+
+ /**
+ * Set the small icon to use in the notification layouts. Different classes of devices
+ * may return different sizes. See the UX guidelines for more information on how to
+ * design these icons.
+ *
+ * @param icon A resource ID in the application's package of the drawble to use.
+ */
+ public Builder setSmallIcon(int icon) {
+ mNotification.icon = icon;
+ return this;
+ }
+
+ /**
+ * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
+ * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
+ * LevelListDrawable}.
+ *
+ * @param icon A resource ID in the application's package of the drawble to use.
+ * @param level The level to use for the icon.
+ *
+ * @see android.graphics.drawable.LevelListDrawable
+ */
+ public Builder setSmallIcon(int icon, int level) {
+ mNotification.icon = icon;
+ mNotification.iconLevel = level;
+ return this;
+ }
+
+ /**
+ * Set the title (first row) of the notification, in a standard notification.
+ */
+ public Builder setContentTitle(CharSequence title) {
+ mContentTitle = title;
+ return this;
+ }
+
+ /**
+ * Set the text (second row) of the notification, in a standard notification.
+ */
+ public Builder setContentText(CharSequence text) {
+ mContentText = text;
+ return this;
+ }
+
+ /**
+ * Set the third line of text in the platform notification template.
+ * Don't use if you're also using {@link #setProgress(int, int, boolean)};
+ * they occupy the same location in the standard template.
+ * <br>
+ * If the platform does not provide large-format notifications, this method has no effect.
+ * The third line of text only appears in expanded view.
+ * <br>
+ */
+ public Builder setSubText(CharSequence text) {
+ mSubText = text;
+ return this;
+ }
+
+ /**
+ * Set the large number at the right-hand side of the notification. This is
+ * equivalent to setContentInfo, although it might show the number in a different
+ * font size for readability.
+ */
+ public Builder setNumber(int number) {
+ mNumber = number;
+ return this;
+ }
+
+ /**
+ * Set the large text at the right-hand side of the notification.
+ */
+ public Builder setContentInfo(CharSequence info) {
+ mContentInfo = info;
+ return this;
+ }
+
+ /**
+ * Set the progress this notification represents, which may be
+ * represented as a {@link android.widget.ProgressBar}.
+ */
+ public Builder setProgress(int max, int progress, boolean indeterminate) {
+ mProgressMax = max;
+ mProgress = progress;
+ mProgressIndeterminate = indeterminate;
+ return this;
+ }
+
+ /**
+ * Supply a custom RemoteViews to use instead of the standard one.
+ */
+ public Builder setContent(RemoteViews views) {
+ mNotification.contentView = views;
+ return this;
+ }
+
+ /**
+ * Supply a {@link PendingIntent} to send when the notification is clicked.
+ * If you do not supply an intent, you can now add PendingIntents to individual
+ * views to be launched when clicked by calling {@link RemoteViews#setOnClickPendingIntent
+ * RemoteViews.setOnClickPendingIntent(int,PendingIntent)}. Be sure to
+ * read {@link Notification#contentIntent Notification.contentIntent} for
+ * how to correctly use this.
+ */
+ public Builder setContentIntent(PendingIntent intent) {
+ mContentIntent = intent;
+ return this;
+ }
+
+ /**
+ * Supply a {@link PendingIntent} to send when the notification is cleared by the user
+ * directly from the notification panel. For example, this intent is sent when the user
+ * clicks the "Clear all" button, or the individual "X" buttons on notifications. This
+ * intent is not sent when the application calls {@link NotificationManager#cancel
+ * NotificationManager.cancel(int)}.
+ */
+ public Builder setDeleteIntent(PendingIntent intent) {
+ mNotification.deleteIntent = intent;
+ return this;
+ }
+
+ /**
+ * An intent to launch instead of posting the notification to the status bar.
+ * Only for use with extremely high-priority notifications demanding the user's
+ * <strong>immediate</strong> attention, such as an incoming phone call or
+ * alarm clock that the user has explicitly set to a particular time.
+ * If this facility is used for something else, please give the user an option
+ * to turn it off and use a normal notification, as this can be extremely
+ * disruptive.
+ *
+ * @param intent The pending intent to launch.
+ * @param highPriority Passing true will cause this notification to be sent
+ * even if other notifications are suppressed.
+ */
+ public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
+ mFullScreenIntent = intent;
+ setFlag(FLAG_HIGH_PRIORITY, highPriority);
+ return this;
+ }
+
+ /**
+ * Set the text that is displayed in the status bar when the notification first
+ * arrives.
+ */
+ public Builder setTicker(CharSequence tickerText) {
+ mNotification.tickerText = tickerText;
+ return this;
+ }
+
+ /**
+ * Set the text that is displayed in the status bar when the notification first
+ * arrives, and also a RemoteViews object that may be displayed instead on some
+ * devices.
+ */
+ public Builder setTicker(CharSequence tickerText, RemoteViews views) {
+ mNotification.tickerText = tickerText;
+ mTickerView = views;
+ return this;
+ }
+
+ /**
+ * Set the large icon that is shown in the ticker and notification.
+ */
+ public Builder setLargeIcon(Bitmap icon) {
+ mLargeIcon = icon;
+ return this;
+ }
+
+ /**
+ * Set the sound to play. It will play on the default stream.
+ */
+ public Builder setSound(Uri sound) {
+ mNotification.sound = sound;
+ mNotification.audioStreamType = Notification.STREAM_DEFAULT;
+ return this;
+ }
+
+ /**
+ * Set the sound to play. It will play on the stream you supply.
+ *
+ * @see #STREAM_DEFAULT
+ * @see AudioManager for the <code>STREAM_</code> constants.
+ */
+ public Builder setSound(Uri sound, int streamType) {
+ mNotification.sound = sound;
+ mNotification.audioStreamType = streamType;
+ return this;
+ }
+
+ /**
+ * Set the vibration pattern to use.
+ *
+ * @see android.os.Vibrator for a discussion of the <code>pattern</code>
+ * parameter.
+ */
+ public Builder setVibrate(long[] pattern) {
+ mNotification.vibrate = pattern;
+ return this;
+ }
+
+ /**
+ * Set the argb value that you would like the LED on the device to blnk, as well as the
+ * rate. The rate is specified in terms of the number of milliseconds to be on
+ * and then the number of milliseconds to be off.
+ */
+ public Builder setLights(int argb, int onMs, int offMs) {
+ mNotification.ledARGB = argb;
+ mNotification.ledOnMS = onMs;
+ mNotification.ledOffMS = offMs;
+ boolean showLights = mNotification.ledOnMS != 0 && mNotification.ledOffMS != 0;
+ mNotification.flags = (mNotification.flags & ~Notification.FLAG_SHOW_LIGHTS) |
+ (showLights ? Notification.FLAG_SHOW_LIGHTS : 0);
+ return this;
+ }
+
+ /**
+ * Set whether this is an ongoing notification.
+ *
+ * <p>Ongoing notifications differ from regular notifications in the following ways:
+ * <ul>
+ * <li>Ongoing notifications are sorted above the regular notifications in the
+ * notification panel.</li>
+ * <li>Ongoing notifications do not have an 'X' close button, and are not affected
+ * by the "Clear all" button.
+ * </ul>
+ */
+ public Builder setOngoing(boolean ongoing) {
+ setFlag(Notification.FLAG_ONGOING_EVENT, ongoing);
+ return this;
+ }
+
+ /**
+ * Set this flag if you would only like the sound, vibrate
+ * and ticker to be played if the notification is not already showing.
+ */
+ public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
+ setFlag(Notification.FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
+ return this;
+ }
+
+ /**
+ * Setting this flag will make it so the notification is automatically
+ * canceled when the user clicks it in the panel. The PendingIntent
+ * set with {@link #setDeleteIntent} will be broadcast when the notification
+ * is canceled.
+ */
+ public Builder setAutoCancel(boolean autoCancel) {
+ setFlag(Notification.FLAG_AUTO_CANCEL, autoCancel);
+ return this;
+ }
+
+ /**
+ * Set the default notification options that will be used.
+ * <p>
+ * The value should be one or more of the following fields combined with
+ * bitwise-or:
+ * {@link Notification#DEFAULT_SOUND}, {@link Notification#DEFAULT_VIBRATE},
+ * {@link Notification#DEFAULT_LIGHTS}.
+ * <p>
+ * For all default values, use {@link Notification#DEFAULT_ALL}.
+ */
+ public Builder setDefaults(int defaults) {
+ mNotification.defaults = defaults;
+ if ((defaults & Notification.DEFAULT_LIGHTS) != 0) {
+ mNotification.flags |= Notification.FLAG_SHOW_LIGHTS;
+ }
+ return this;
+ }
+
+ private void setFlag(int mask, boolean value) {
+ if (value) {
+ mNotification.flags |= mask;
+ } else {
+ mNotification.flags &= ~mask;
+ }
+ }
+
+ /**
+ * Set the relative priority for this notification.
+ *
+ * Priority is an indication of how much of the user's
+ * valuable attention should be consumed by this
+ * notification. Low-priority notifications may be hidden from
+ * the user in certain situations, while the user might be
+ * interrupted for a higher-priority notification.
+ * The system sets a notification's priority based on various factors including the
+ * setPriority value. The effect may differ slightly on different platforms.
+ */
+ public Builder setPriority(int pri) {
+ mPriority = pri;
+ return this;
+ }
+
+ /**
+ * Add an action to this notification. Actions are typically displayed by
+ * the system as a button adjacent to the notification content.
+ * <br>
+ * Action buttons won't appear on platforms prior to Android 4.1. Action
+ * buttons depend on expanded notifications, which are only available in Android 4.1
+ * and later. To ensure that an action button's functionality is always available, first
+ * implement the functionality in the {@link android.app.Activity} that starts when a user
+ * clicks the notification (see {@link #setContentIntent setContentIntent()}), and then
+ * enhance the notification by implementing the same functionality with
+ * {@link #addAction addAction()}.
+ *
+ * @param icon Resource ID of a drawable that represents the action.
+ * @param title Text describing the action.
+ * @param intent {@link android.app.PendingIntent} to be fired when the action is invoked.
+ */
+ public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
+ mActions.add(new Action(icon, title, intent));
+ return this;
+ }
+
+ /**
+ * Add a rich notification style to be applied at build time.
+ * <br>
+ * If the platform does not provide rich notification styles, this method has no effect. The
+ * user will always see the normal notification style.
+ *
+ * @param style Object responsible for modifying the notification style.
+ */
+ public Builder setStyle(Style style) {
+ if (mStyle != style) {
+ mStyle = style;
+ if (mStyle != null) {
+ mStyle.setBuilder(this);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * @deprecated Use {@link #build()} instead.
+ */
+ @Deprecated
+ public Notification getNotification() {
+ return IMPL.build(this);
+ }
+
+ /**
+ * Combine all of the options that have been set and return a new {@link Notification}
+ * object.
+ */
+ public Notification build() {
+ return IMPL.build(this);
+ }
+ }
+
+ /**
+ * An object that can apply a rich notification style to a {@link Notification.Builder}
+ * object.
+ * <br>
+ * If the platform does not provide rich notification styles, methods in this class have no
+ * effect.
+ */
+ public static abstract class Style
+ {
+ Builder mBuilder;
+ CharSequence mBigContentTitle;
+ CharSequence mSummaryText;
+ boolean mSummaryTextSet = false;
+
+ public void setBuilder(Builder builder) {
+ if (mBuilder != builder) {
+ mBuilder = builder;
+ if (mBuilder != null) {
+ mBuilder.setStyle(this);
+ }
+ }
+ }
+
+ public Notification build() {
+ Notification notification = null;
+ if (mBuilder != null) {
+ notification = mBuilder.build();
+ }
+ return notification;
+ }
+ }
+
+ /**
+ * Helper class for generating large-format notifications that include a large image attachment.
+ * <br>
+ * If the platform does not provide large-format notifications, this method has no effect. The
+ * user will always see the normal notification view.
+ * <br>
+ * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so:
+ * <pre class="prettyprint">
+ * Notification noti = new Notification.Builder()
+ * .setContentTitle("New photo from " + sender.toString())
+ * .setContentText(subject)
+ * .setSmallIcon(R.drawable.new_post)
+ * .setLargeIcon(aBitmap)
+ * .setStyle(new Notification.BigPictureStyle()
+ * .bigPicture(aBigBitmap))
+ * .build();
+ * </pre>
+ *
+ * @see Notification#bigContentView
+ */
+ public static class BigPictureStyle extends Style {
+ Bitmap mPicture;
+
+ public BigPictureStyle() {
+ }
+
+ public BigPictureStyle(Builder builder) {
+ setBuilder(builder);
+ }
+
+ /**
+ * Overrides ContentTitle in the big form of the template.
+ * This defaults to the value passed to setContentTitle().
+ */
+ public BigPictureStyle setBigContentTitle(CharSequence title) {
+ mBigContentTitle = title;
+ return this;
+ }
+
+ /**
+ * Set the first line of text after the detail section in the big form of the template.
+ */
+ public BigPictureStyle setSummaryText(CharSequence cs) {
+ mSummaryText = cs;
+ mSummaryTextSet = true;
+ return this;
+ }
+
+ /**
+ * Provide the bitmap to be used as the payload for the BigPicture notification.
+ */
+ public BigPictureStyle bigPicture(Bitmap b) {
+ mPicture = b;
+ return this;
+ }
+ }
+
+ /**
+ * Helper class for generating large-format notifications that include a lot of text.
+ *
+ * <br>
+ * If the platform does not provide large-format notifications, this method has no effect. The
+ * user will always see the normal notification view.
+ * <br>
+ * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so:
+ * <pre class="prettyprint">
+ * Notification noti = new Notification.Builder()
+ * .setContentTitle("New mail from " + sender.toString())
+ * .setContentText(subject)
+ * .setSmallIcon(R.drawable.new_mail)
+ * .setLargeIcon(aBitmap)
+ * .setStyle(new Notification.BigTextStyle()
+ * .bigText(aVeryLongString))
+ * .build();
+ * </pre>
+ *
+ * @see Notification#bigContentView
+ */
+ public static class BigTextStyle extends Style {
+ CharSequence mBigText;
+
+ public BigTextStyle() {
+ }
+
+ public BigTextStyle(Builder builder) {
+ setBuilder(builder);
+ }
+
+ /**
+ * Overrides ContentTitle in the big form of the template.
+ * This defaults to the value passed to setContentTitle().
+ */
+ public BigTextStyle setBigContentTitle(CharSequence title) {
+ mBigContentTitle = title;
+ return this;
+ }
+
+ /**
+ * Set the first line of text after the detail section in the big form of the template.
+ */
+ public BigTextStyle setSummaryText(CharSequence cs) {
+ mSummaryText = cs;
+ mSummaryTextSet = true;
+ return this;
+ }
+
+ /**
+ * Provide the longer text to be displayed in the big form of the
+ * template in place of the content text.
+ */
+ public BigTextStyle bigText(CharSequence cs) {
+ mBigText = cs;
+ return this;
+ }
+ }
+
+ /**
+ * Helper class for generating large-format notifications that include a list of (up to 5) strings.
+ *
+ * <br>
+ * If the platform does not provide large-format notifications, this method has no effect. The
+ * user will always see the normal notification view.
+ * <br>
+ * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so:
+ * <pre class="prettyprint">
+ * Notification noti = new Notification.Builder()
+ * .setContentTitle("5 New mails from " + sender.toString())
+ * .setContentText(subject)
+ * .setSmallIcon(R.drawable.new_mail)
+ * .setLargeIcon(aBitmap)
+ * .setStyle(new Notification.InboxStyle()
+ * .addLine(str1)
+ * .addLine(str2)
+ * .setContentTitle("")
+ * .setSummaryText("+3 more"))
+ * .build();
+ * </pre>
+ *
+ * @see Notification#bigContentView
+ */
+ public static class InboxStyle extends Style {
+ ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+
+ public InboxStyle() {
+ }
+
+ public InboxStyle(Builder builder) {
+ setBuilder(builder);
+ }
+
+ /**
+ * Overrides ContentTitle in the big form of the template.
+ * This defaults to the value passed to setContentTitle().
+ */
+ public InboxStyle setBigContentTitle(CharSequence title) {
+ mBigContentTitle = title;
+ return this;
+ }
+
+ /**
+ * Set the first line of text after the detail section in the big form of the template.
+ */
+ public InboxStyle setSummaryText(CharSequence cs) {
+ mSummaryText = cs;
+ mSummaryTextSet = true;
+ return this;
+ }
+
+ /**
+ * Append a line to the digest section of the Inbox notification.
+ */
+ public InboxStyle addLine(CharSequence cs) {
+ mTexts.add(cs);
+ return this;
+ }
+ }
+
+ public static class Action {
+ public int icon;
+ public CharSequence title;
+ public PendingIntent actionIntent;
+
+ public Action(int icon_, CharSequence title_, PendingIntent intent_) {
+ this.icon = icon_;
+ this.title = title_;
+ this.actionIntent = intent_;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2012 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 android.support.v4.app;
+
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.widget.RemoteViews;
+
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+class NotificationCompatHoneycomb {
+ @SuppressWarnings("deprecation")
+ static Notification add(Context context, Notification n,
+ CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+ RemoteViews tickerView, int number,
+ PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon) {
+ Notification.Builder b = new Notification.Builder(context)
+ .setWhen(n.when)
+ .setSmallIcon(n.icon, n.iconLevel)
+ .setContent(n.contentView)
+ .setTicker(n.tickerText, tickerView)
+ .setSound(n.sound, n.audioStreamType)
+ .setVibrate(n.vibrate)
+ .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+ .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+ .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+ .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+ .setDefaults(n.defaults)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setContentInfo(contentInfo)
+ .setContentIntent(contentIntent)
+ .setDeleteIntent(n.deleteIntent)
+ .setFullScreenIntent(fullScreenIntent,
+ (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+ .setLargeIcon(largeIcon)
+ .setNumber(number);
+
+ return b.getNotification();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2012 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 android.support.v4.app;
+
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.widget.RemoteViews;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+class NotificationCompatIceCreamSandwich {
+ @SuppressWarnings("deprecation")
+ static Notification add(Context context, Notification n,
+ CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+ RemoteViews tickerView, int number,
+ PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+ int mProgressMax, int mProgress, boolean mProgressIndeterminate) {
+ Notification.Builder b = new Notification.Builder(context)
+ .setWhen(n.when)
+ .setSmallIcon(n.icon, n.iconLevel)
+ .setContent(n.contentView)
+ .setTicker(n.tickerText, tickerView)
+ .setSound(n.sound, n.audioStreamType)
+ .setVibrate(n.vibrate)
+ .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+ .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+ .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+ .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+ .setDefaults(n.defaults)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setContentInfo(contentInfo)
+ .setContentIntent(contentIntent)
+ .setDeleteIntent(n.deleteIntent)
+ .setFullScreenIntent(fullScreenIntent,
+ (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+ .setLargeIcon(largeIcon)
+ .setNumber(number)
+ .setProgress(mProgressMax, mProgress, mProgressIndeterminate);
+
+ return b.getNotification();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2012 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 android.support.v4.app;
+
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.widget.RemoteViews;
+import java.util.ArrayList;
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+class NotificationCompatJellybean {
+ private Notification.Builder b;
+ @SuppressWarnings("deprecation")
+ public NotificationCompatJellybean(Context context, Notification n,
+ CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+ RemoteViews tickerView, int number,
+ PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+ int mProgressMax, int mProgress, boolean mProgressIndeterminate,
+ boolean useChronometer, int priority, CharSequence subText) {
+ b = new Notification.Builder(context)
+ .setWhen(n.when)
+ .setSmallIcon(n.icon, n.iconLevel)
+ .setContent(n.contentView)
+ .setTicker(n.tickerText, tickerView)
+ .setSound(n.sound, n.audioStreamType)
+ .setVibrate(n.vibrate)
+ .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+ .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+ .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+ .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+ .setDefaults(n.defaults)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setSubText(subText)
+ .setContentInfo(contentInfo)
+ .setContentIntent(contentIntent)
+ .setDeleteIntent(n.deleteIntent)
+ .setFullScreenIntent(fullScreenIntent,
+ (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+ .setLargeIcon(largeIcon)
+ .setNumber(number)
+ .setUsesChronometer(useChronometer)
+ .setPriority(priority)
+ .setProgress(mProgressMax, mProgress, mProgressIndeterminate);
+ }
+
+ public void addAction(int icon, CharSequence title, PendingIntent intent) {
+ b.addAction(icon, title, intent);
+ }
+
+ public void addBigTextStyle(CharSequence bigContentTitle, boolean useSummary,
+ CharSequence summaryText, CharSequence bigText) {
+ Notification.BigTextStyle style = new Notification.BigTextStyle(b)
+ .setBigContentTitle(bigContentTitle)
+ .bigText(bigText);
+ if (useSummary) {
+ style.setSummaryText(summaryText);
+ }
+ }
+
+ public void addBigPictureStyle(CharSequence bigContentTitle, boolean useSummary,
+ CharSequence summaryText, Bitmap bigPicture) {
+ Notification.BigPictureStyle style = new Notification.BigPictureStyle(b)
+ .setBigContentTitle(bigContentTitle)
+ .bigPicture(bigPicture);
+ if (useSummary) {
+ style.setSummaryText(summaryText);
+ }
+ }
+
+ public void addInboxStyle(CharSequence bigContentTitle, boolean useSummary,
+ CharSequence summaryText, ArrayList<CharSequence> texts) {
+ Notification.InboxStyle style = new Notification.InboxStyle(b)
+ .setBigContentTitle(bigContentTitle);
+ if (useSummary) {
+ style.setSummaryText(summaryText);
+ }
+ for (CharSequence text: texts) {
+ style.addLine(text);
+ }
+ }
+
+ public Notification build() {
+ return b.build();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2011 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 android.support.v4.content;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * Helper to register for and send broadcasts of Intents to local objects
+ * within your process. This is has a number of advantages over sending
+ * global broadcasts with {@link android.content.Context#sendBroadcast}:
+ * <ul>
+ * <li> You know that the data you are broadcasting won't leave your app, so
+ * don't need to worry about leaking private data.
+ * <li> It is not possible for other applications to send these broadcasts to
+ * your app, so you don't need to worry about having security holes they can
+ * exploit.
+ * <li> It is more efficient than sending a global broadcast through the
+ * system.
+ * </ul>
+ */
+public class LocalBroadcastManager {
+ private static class ReceiverRecord {
+ final IntentFilter filter;
+ final BroadcastReceiver receiver;
+ boolean broadcasting;
+
+ ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
+ filter = _filter;
+ receiver = _receiver;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(128);
+ builder.append("Receiver{");
+ builder.append(receiver);
+ builder.append(" filter=");
+ builder.append(filter);
+ builder.append("}");
+ return builder.toString();
+ }
+ }
+
+ private static class BroadcastRecord {
+ final Intent intent;
+ final ArrayList<ReceiverRecord> receivers;
+
+ BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
+ intent = _intent;
+ receivers = _receivers;
+ }
+ }
+
+ private static final String TAG = "LocalBroadcastManager";
+ private static final boolean DEBUG = false;
+
+ private final Context mAppContext;
+
+ private final HashMap<BroadcastReceiver, ArrayList<IntentFilter>> mReceivers
+ = new HashMap<BroadcastReceiver, ArrayList<IntentFilter>>();
+ private final HashMap<String, ArrayList<ReceiverRecord>> mActions
+ = new HashMap<String, ArrayList<ReceiverRecord>>();
+
+ private final ArrayList<BroadcastRecord> mPendingBroadcasts
+ = new ArrayList<BroadcastRecord>();
+
+ static final int MSG_EXEC_PENDING_BROADCASTS = 1;
+
+ private final Handler mHandler;
+
+ private static final Object mLock = new Object();
+ private static LocalBroadcastManager mInstance;
+
+ public static LocalBroadcastManager getInstance(Context context) {
+ synchronized (mLock) {
+ if (mInstance == null) {
+ mInstance = new LocalBroadcastManager(context.getApplicationContext());
+ }
+ return mInstance;
+ }
+ }
+
+ private LocalBroadcastManager(Context context) {
+ mAppContext = context;
+ mHandler = new Handler(context.getMainLooper()) {
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_EXEC_PENDING_BROADCASTS:
+ executePendingBroadcasts();
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ };
+ }
+
+ /**
+ * Register a receive for any local broadcasts that match the given IntentFilter.
+ *
+ * @param receiver The BroadcastReceiver to handle the broadcast.
+ * @param filter Selects the Intent broadcasts to be received.
+ *
+ * @see #unregisterReceiver
+ */
+ public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ synchronized (mReceivers) {
+ ReceiverRecord entry = new ReceiverRecord(filter, receiver);
+ ArrayList<IntentFilter> filters = mReceivers.get(receiver);
+ if (filters == null) {
+ filters = new ArrayList<IntentFilter>(1);
+ mReceivers.put(receiver, filters);
+ }
+ filters.add(filter);
+ for (int i=0; i<filter.countActions(); i++) {
+ String action = filter.getAction(i);
+ ArrayList<ReceiverRecord> entries = mActions.get(action);
+ if (entries == null) {
+ entries = new ArrayList<ReceiverRecord>(1);
+ mActions.put(action, entries);
+ }
+ entries.add(entry);
+ }
+ }
+ }
+
+ /**
+ * Unregister a previously registered BroadcastReceiver. <em>All</em>
+ * filters that have been registered for this BroadcastReceiver will be
+ * removed.
+ *
+ * @param receiver The BroadcastReceiver to unregister.
+ *
+ * @see #registerReceiver
+ */
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ synchronized (mReceivers) {
+ ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
+ if (filters == null) {
+ return;
+ }
+ for (int i=0; i<filters.size(); i++) {
+ IntentFilter filter = filters.get(i);
+ for (int j=0; j<filter.countActions(); j++) {
+ String action = filter.getAction(j);
+ ArrayList<ReceiverRecord> receivers = mActions.get(action);
+ if (receivers != null) {
+ for (int k=0; k<receivers.size(); k++) {
+ if (receivers.get(k).receiver == receiver) {
+ receivers.remove(k);
+ k--;
+ }
+ }
+ if (receivers.size() <= 0) {
+ mActions.remove(action);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Broadcast the given intent to all interested BroadcastReceivers. This
+ * call is asynchronous; it returns immediately, and you will continue
+ * executing while the receivers are run.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ *
+ * @see #registerReceiver
+ */
+ public boolean sendBroadcast(Intent intent) {
+ synchronized (mReceivers) {
+ final String action = intent.getAction();
+ final String type = intent.resolveTypeIfNeeded(
+ mAppContext.getContentResolver());
+ final Uri data = intent.getData();
+ final String scheme = intent.getScheme();
+ final Set<String> categories = intent.getCategories();
+
+ final boolean debug = DEBUG ||
+ ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+ if (debug) Log.v(
+ TAG, "Resolving type " + type + " scheme " + scheme
+ + " of intent " + intent);
+
+ ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
+ if (entries != null) {
+ if (debug) Log.v(TAG, "Action list: " + entries);
+
+ ArrayList<ReceiverRecord> receivers = null;
+ for (int i=0; i<entries.size(); i++) {
+ ReceiverRecord receiver = entries.get(i);
+ if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);
+
+ if (receiver.broadcasting) {
+ if (debug) {
+ Log.v(TAG, " Filter's target already added");
+ }
+ continue;
+ }
+
+ int match = receiver.filter.match(action, type, scheme, data,
+ categories, "LocalBroadcastManager");
+ if (match >= 0) {
+ if (debug) Log.v(TAG, " Filter matched! match=0x" +
+ Integer.toHexString(match));
+ if (receivers == null) {
+ receivers = new ArrayList<ReceiverRecord>();
+ }
+ receivers.add(receiver);
+ receiver.broadcasting = true;
+ } else {
+ if (debug) {
+ String reason;
+ switch (match) {
+ case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
+ case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
+ case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
+ case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
+ default: reason = "unknown reason"; break;
+ }
+ Log.v(TAG, " Filter did not match: " + reason);
+ }
+ }
+ }
+
+ if (receivers != null) {
+ for (int i=0; i<receivers.size(); i++) {
+ receivers.get(i).broadcasting = false;
+ }
+ mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
+ if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
+ mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Like {@link #sendBroadcast(Intent)}, but if there are any receivers for
+ * the Intent this function will block and immediately dispatch them before
+ * returning.
+ */
+ public void sendBroadcastSync(Intent intent) {
+ if (sendBroadcast(intent)) {
+ executePendingBroadcasts();
+ }
+ }
+
+ private void executePendingBroadcasts() {
+ while (true) {
+ BroadcastRecord[] brs = null;
+ synchronized (mReceivers) {
+ final int N = mPendingBroadcasts.size();
+ if (N <= 0) {
+ return;
+ }
+ brs = new BroadcastRecord[N];
+ mPendingBroadcasts.toArray(brs);
+ mPendingBroadcasts.clear();
+ }
+ for (int i=0; i<brs.length; i++) {
+ BroadcastRecord br = brs[i];
+ for (int j=0; j<br.receivers.size(); j++) {
+ br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * 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.google.android.gcm;
+
+import static com.google.android.gcm.GCMConstants.ERROR_SERVICE_NOT_AVAILABLE;
+import static com.google.android.gcm.GCMConstants.EXTRA_ERROR;
+import static com.google.android.gcm.GCMConstants.EXTRA_REGISTRATION_ID;
+import static com.google.android.gcm.GCMConstants.EXTRA_SPECIAL_MESSAGE;
+import static com.google.android.gcm.GCMConstants.EXTRA_TOTAL_DELETED;
+import static com.google.android.gcm.GCMConstants.EXTRA_UNREGISTERED;
+import static com.google.android.gcm.GCMConstants.INTENT_FROM_GCM_LIBRARY_RETRY;
+import static com.google.android.gcm.GCMConstants.INTENT_FROM_GCM_MESSAGE;
+import static com.google.android.gcm.GCMConstants.INTENT_FROM_GCM_REGISTRATION_CALLBACK;
+import static com.google.android.gcm.GCMConstants.VALUE_DELETED_MESSAGES;
+
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Skeleton for application-specific {@link IntentService}s responsible for
+ * handling communication from Google Cloud Messaging service.
+ * <p>
+ * The abstract methods in this class are called from its worker thread, and
+ * hence should run in a limited amount of time. If they execute long
+ * operations, they should spawn new threads, otherwise the worker thread will
+ * be blocked.
+ * <p>
+ * Subclasses must provide a public no-arg constructor.
+ */
+public abstract class GCMBaseIntentService extends IntentService {
+
+ public static final String TAG = "GCMBaseIntentService";
+
+ // wakelock
+ private static final String WAKELOCK_KEY = "GCM_LIB";
+ private static PowerManager.WakeLock sWakeLock;
+
+ // Java lock used to synchronize access to sWakelock
+ private static final Object LOCK = GCMBaseIntentService.class;
+
+ private final String[] mSenderIds;
+
+ // instance counter
+ private static int sCounter = 0;
+
+ private static final Random sRandom = new Random();
+
+ private static final int MAX_BACKOFF_MS =
+ (int) TimeUnit.SECONDS.toMillis(3600); // 1 hour
+
+ // token used to check intent origin
+ private static final String TOKEN =
+ Long.toBinaryString(sRandom.nextLong());
+ private static final String EXTRA_TOKEN = "token";
+
+ /**
+ * Constructor that does not set a sender id, useful when the sender id
+ * is context-specific.
+ * <p>
+ * When using this constructor, the subclass <strong>must</strong>
+ * override {@link #getSenderIds(Context)}, otherwise methods such as
+ * {@link #onHandleIntent(Intent)} will throw an
+ * {@link IllegalStateException} on runtime.
+ */
+ protected GCMBaseIntentService() {
+ this(getName("DynamicSenderIds"), null);
+ }
+
+ /**
+ * Constructor used when the sender id(s) is fixed.
+ */
+ protected GCMBaseIntentService(String... senderIds) {
+ this(getName(senderIds), senderIds);
+ }
+
+ private GCMBaseIntentService(String name, String[] senderIds) {
+ super(name); // name is used as base name for threads, etc.
+ mSenderIds = senderIds;
+ }
+
+ private static String getName(String senderId) {
+ String name = "GCMIntentService-" + senderId + "-" + (++sCounter);
+ Log.v(TAG, "Intent service name: " + name);
+ return name;
+ }
+
+ private static String getName(String[] senderIds) {
+ String flatSenderIds = GCMRegistrar.getFlatSenderIds(senderIds);
+ return getName(flatSenderIds);
+ }
+
+ /**
+ * Gets the sender ids.
+ *
+ * <p>By default, it returns the sender ids passed in the constructor, but
+ * it could be overridden to provide a dynamic sender id.
+ *
+ * @throws IllegalStateException if sender id was not set on constructor.
+ */
+ protected String[] getSenderIds() {
+ if (mSenderIds == null) {
+ throw new IllegalStateException("sender id not set on constructor");
+ }
+ return mSenderIds;
+ }
+
+ /**
+ * Called when a cloud message has been received.
+ *
+ * @param context application's context.
+ * @param intent intent containing the message payload as extras.
+ */
+ protected abstract void onMessage(Context context, Intent intent);
+
+ /**
+ * Called when the GCM server tells pending messages have been deleted
+ * because the device was idle.
+ *
+ * @param context application's context.
+ * @param total total number of collapsed messages
+ */
+ protected void onDeletedMessages(Context context, int total) {
+ //do nothing
+ }
+
+ /**
+ * Called on a registration error that could be retried.
+ *
+ * <p>By default, it does nothing and returns {@literal true}, but could be
+ * overridden to change that behavior and/or display the error.
+ *
+ * @param context application's context.
+ * @param errorId error id returned by the GCM service.
+ *
+ * @return if {@literal true}, failed operation will be retried (using
+ * exponential backoff).
+ */
+ protected static boolean onRecoverableError(Context context, String errorId) {
+ return true;
+ }
+
+ /**
+ * Called on registration or unregistration error.
+ *
+ * @param context application's context.
+ * @param errorId error id returned by the GCM service.
+ */
+ protected abstract void onError(Context context, String errorId);
+
+ /**
+ * Called after a device has been registered.
+ *
+ * @param context application's context.
+ * @param registrationId the registration id returned by the GCM service.
+ */
+ protected abstract void onRegistered(Context context,
+ String registrationId);
+
+ /**
+ * Called after a device has been unregistered.
+ *
+ * @param registrationId the registration id that was previously registered.
+ * @param context application's context.
+ */
+ protected abstract void onUnregistered(Context context,
+ String registrationId);
+
+ @Override
+ public final void onHandleIntent(Intent intent) {
+ try {
+ Context context = getApplicationContext();
+ String action = intent.getAction();
+ if (action.equals(INTENT_FROM_GCM_REGISTRATION_CALLBACK)) {
+ GCMRegistrar.setRetryBroadcastReceiver(context);
+ handleRegistration(context, intent);
+ } else if (action.equals(INTENT_FROM_GCM_MESSAGE)) {
+ // checks for special messages
+ String messageType =
+ intent.getStringExtra(EXTRA_SPECIAL_MESSAGE);
+ if (messageType != null) {
+ if (messageType.equals(VALUE_DELETED_MESSAGES)) {
+ String sTotal =
+ intent.getStringExtra(EXTRA_TOTAL_DELETED);
+ if (sTotal != null) {
+ try {
+ int total = Integer.parseInt(sTotal);
+ Log.v(TAG, "Received deleted messages " +
+ "notification: " + total);
+ onDeletedMessages(context, total);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "GCM returned invalid number of " +
+ "deleted messages: " + sTotal);
+ }
+ }
+ } else {
+ // application is not using the latest GCM library
+ Log.e(TAG, "Received unknown special message: " +
+ messageType);
+ }
+ } else {
+ onMessage(context, intent);
+ }
+ } else if (action.equals(INTENT_FROM_GCM_LIBRARY_RETRY)) {
+ String token = intent.getStringExtra(EXTRA_TOKEN);
+ if (!TOKEN.equals(token)) {
+ // make sure intent was generated by this class, not by a
+ // malicious app.
+ Log.e(TAG, "Received invalid token: " + token);
+ return;
+ }
+ // retry last call
+ if (GCMRegistrar.isRegistered(context)) {
+ GCMRegistrar.internalUnregister(context);
+ } else {
+ String[] senderIds = getSenderIds();
+ GCMRegistrar.internalRegister(context, senderIds);
+ }
+ }
+ } finally {
+ // Release the power lock, so phone can get back to sleep.
+ // The lock is reference-counted by default, so multiple
+ // messages are ok.
+
+ // If onMessage() needs to spawn a thread or do something else,
+ // it should use its own lock.
+ synchronized (LOCK) {
+ // sanity check for null as this is a public method
+ if (sWakeLock != null) {
+ Log.v(TAG, "Releasing wakelock");
+ sWakeLock.release();
+ } else {
+ // should never happen during normal workflow
+ Log.e(TAG, "Wakelock reference is null");
+ }
+ }
+ }
+ }
+
+ /**
+ * Called from the broadcast receiver.
+ * <p>
+ * Will process the received intent, call handleMessage(), registered(),
+ * etc. in background threads, with a wake lock, while keeping the service
+ * alive.
+ */
+ static void runIntentInService(Context context, Intent intent,
+ String className) {
+ synchronized (LOCK) {
+ if (sWakeLock == null) {
+ // This is called from BroadcastReceiver, there is no init.
+ PowerManager pm = (PowerManager)
+ context.getSystemService(Context.POWER_SERVICE);
+ sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ WAKELOCK_KEY);
+ }
+ }
+ Log.v(TAG, "Acquiring wakelock");
+ sWakeLock.acquire();
+ intent.setClassName(context, className);
+ context.startService(intent);
+ }
+
+ private void handleRegistration(final Context context, Intent intent) {
+ String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID);
+ String error = intent.getStringExtra(EXTRA_ERROR);
+ String unregistered = intent.getStringExtra(EXTRA_UNREGISTERED);
+ Log.d(TAG, "handleRegistration: registrationId = " + registrationId +
+ ", error = " + error + ", unregistered = " + unregistered);
+
+ // registration succeeded
+ if (registrationId != null) {
+ GCMRegistrar.resetBackoff(context);
+ GCMRegistrar.setRegistrationId(context, registrationId);
+ onRegistered(context, registrationId);
+ return;
+ }
+
+ // unregistration succeeded
+ if (unregistered != null) {
+ // Remember we are unregistered
+ GCMRegistrar.resetBackoff(context);
+ String oldRegistrationId =
+ GCMRegistrar.clearRegistrationId(context);
+ onUnregistered(context, oldRegistrationId);
+ return;
+ }
+
+ // last operation (registration or unregistration) returned an error;
+ Log.d(TAG, "Registration error: " + error);
+ // Registration failed
+ if (ERROR_SERVICE_NOT_AVAILABLE.equals(error)) {
+ boolean retry = onRecoverableError(context, error);
+ if (retry) {
+ int backoffTimeMs = GCMRegistrar.getBackoff(context);
+ int nextAttempt = backoffTimeMs / 2 +
+ sRandom.nextInt(backoffTimeMs);
+ Log.d(TAG, "Scheduling registration retry, backoff = " +
+ nextAttempt + " (" + backoffTimeMs + ")");
+ Intent retryIntent =
+ new Intent(INTENT_FROM_GCM_LIBRARY_RETRY);
+ retryIntent.putExtra(EXTRA_TOKEN, TOKEN);
+ PendingIntent retryPendingIntent = PendingIntent
+ .getBroadcast(context, 0, retryIntent, 0);
+ AlarmManager am = (AlarmManager)
+ context.getSystemService(Context.ALARM_SERVICE);
+ am.set(AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + nextAttempt,
+ retryPendingIntent);
+ // Next retry should wait longer.
+ if (backoffTimeMs < MAX_BACKOFF_MS) {
+ GCMRegistrar.setBackoff(context, backoffTimeMs * 2);
+ }
+ } else {
+ Log.d(TAG, "Not retrying failed operation");
+ }
+ } else {
+ // Unrecoverable error, notify app
+ onError(context, error);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * 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.google.android.gcm;
+
+import static com.google.android.gcm.GCMConstants.DEFAULT_INTENT_SERVICE_CLASS_NAME;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * {@link BroadcastReceiver} that receives GCM messages and delivers them to
+ * an application-specific {@link GCMBaseIntentService} subclass.
+ * <p>
+ * By default, the {@link GCMBaseIntentService} class belongs to the application
+ * main package and is named
+ * {@link GCMConstants#DEFAULT_INTENT_SERVICE_CLASS_NAME}. To use a new class,
+ * the {@link #getGCMIntentServiceClassName(Context)} must be overridden.
+ */
+public final class GCMBroadcastReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "GCMBroadcastReceiver";
+ private static boolean mReceiverSet = false;
+
+ @Override
+ public final void onReceive(Context context, Intent intent) {
+ Log.v(TAG, "onReceive: " + intent.getAction());
+ // do a one-time check if app is using a custom GCMBroadcastReceiver
+ if (!mReceiverSet) {
+ mReceiverSet = true;
+ String myClass = getClass().getName();
+ if (!myClass.equals(GCMBroadcastReceiver.class.getName())) {
+ GCMRegistrar.setRetryReceiverClassName(myClass);
+ }
+ }
+ String className = getGCMIntentServiceClassName(context);
+ Log.v(TAG, "GCM IntentService class: " + className);
+ // Delegates to the application-specific intent service.
+ GCMBaseIntentService.runIntentInService(context, intent, className);
+ setResult(Activity.RESULT_OK, null /* data */, null /* extra */);
+ }
+
+ /**
+ * Gets the class name of the intent service that will handle GCM messages.
+ */
+ protected static String getGCMIntentServiceClassName(final Context context) {
+ return getDefaultIntentServiceClassName(context);
+ }
+
+ /**
+ * Gets the default class name of the intent service that will handle GCM
+ * messages.
+ */
+ private static final String getDefaultIntentServiceClassName(final Context context) {
+ String className = context.getPackageName() +
+ DEFAULT_INTENT_SERVICE_CLASS_NAME;
+ return className;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * 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.google.android.gcm;
+
+/**
+ * Constants used by the GCM library.
+ */
+public final class GCMConstants {
+
+ /**
+ * Intent sent to GCM to register the application.
+ */
+ public static final String INTENT_TO_GCM_REGISTRATION =
+ "com.google.android.c2dm.intent.REGISTER";
+
+ /**
+ * Intent sent to GCM to unregister the application.
+ */
+ public static final String INTENT_TO_GCM_UNREGISTRATION =
+ "com.google.android.c2dm.intent.UNREGISTER";
+
+ /**
+ * Intent sent by GCM indicating with the result of a registration request.
+ */
+ public static final String INTENT_FROM_GCM_REGISTRATION_CALLBACK =
+ "com.google.android.c2dm.intent.REGISTRATION";
+
+ /**
+ * Intent used by the GCM library to indicate that the registration call
+ * should be retried.
+ */
+ public static final String INTENT_FROM_GCM_LIBRARY_RETRY =
+ "com.google.android.gcm.intent.RETRY";
+
+ /**
+ * Intent sent by GCM containing a message.
+ */
+ public static final String INTENT_FROM_GCM_MESSAGE =
+ "com.google.android.c2dm.intent.RECEIVE";
+
+ /**
+ * Extra used on {@link #INTENT_TO_GCM_REGISTRATION} to indicate the sender
+ * account (a Google email) that owns the application.
+ */
+ public static final String EXTRA_SENDER = "sender";
+
+ /**
+ * Extra used on {@link #INTENT_TO_GCM_REGISTRATION} to get the application
+ * id.
+ */
+ public static final String EXTRA_APPLICATION_PENDING_INTENT = "app";
+
+ /**
+ * Extra used on {@link #INTENT_FROM_GCM_REGISTRATION_CALLBACK} to indicate
+ * that the application has been unregistered.
+ */
+ public static final String EXTRA_UNREGISTERED = "unregistered";
+
+ /**
+ * Extra used on {@link #INTENT_FROM_GCM_REGISTRATION_CALLBACK} to indicate
+ * an error when the registration fails. See constants starting with ERROR_
+ * for possible values.
+ */
+ public static final String EXTRA_ERROR = "error";
+
+ /**
+ * Extra used on {@link #INTENT_FROM_GCM_REGISTRATION_CALLBACK} to indicate
+ * the registration id when the registration succeeds.
+ */
+ public static final String EXTRA_REGISTRATION_ID = "registration_id";
+
+ /**
+ * Type of message present in the {@link #INTENT_FROM_GCM_MESSAGE} intent.
+ * This extra is only set for special messages sent from GCM, not for
+ * messages originated from the application.
+ */
+ public static final String EXTRA_SPECIAL_MESSAGE = "message_type";
+
+ /**
+ * Special message indicating the server deleted the pending messages.
+ */
+ public static final String VALUE_DELETED_MESSAGES = "deleted_messages";
+
+ /**
+ * Number of messages deleted by the server because the device was idle.
+ * Present only on messages of special type
+ * {@link #VALUE_DELETED_MESSAGES}
+ */
+ public static final String EXTRA_TOTAL_DELETED = "total_deleted";
+
+ /**
+ * Permission necessary to receive GCM intents.
+ */
+ public static final String PERMISSION_GCM_INTENTS =
+ "com.google.android.c2dm.permission.SEND";
+
+ /**
+ * @see GCMBroadcastReceiver
+ */
+ public static final String DEFAULT_INTENT_SERVICE_CLASS_NAME =
+ ".GCMIntentService";
+
+ /**
+ * The device can't read the response, or there was a 500/503 from the
+ * server that can be retried later. The application should use exponential
+ * back off and retry.
+ */
+ public static final String ERROR_SERVICE_NOT_AVAILABLE =
+ "SERVICE_NOT_AVAILABLE";
+
+ /**
+ * There is no Google account on the phone. The application should ask the
+ * user to open the account manager and add a Google account.
+ */
+ public static final String ERROR_ACCOUNT_MISSING =
+ "ACCOUNT_MISSING";
+
+ /**
+ * Bad password. The application should ask the user to enter his/her
+ * password, and let user retry manually later. Fix on the device side.
+ */
+ public static final String ERROR_AUTHENTICATION_FAILED =
+ "AUTHENTICATION_FAILED";
+
+ /**
+ * The request sent by the phone does not contain the expected parameters.
+ * This phone doesn't currently support GCM.
+ */
+ public static final String ERROR_INVALID_PARAMETERS =
+ "INVALID_PARAMETERS";
+ /**
+ * The sender account is not recognized. Fix on the device side.
+ */
+ public static final String ERROR_INVALID_SENDER =
+ "INVALID_SENDER";
+
+ /**
+ * Incorrect phone registration with Google. This phone doesn't currently
+ * support GCM.
+ */
+ public static final String ERROR_PHONE_REGISTRATION_ERROR =
+ "PHONE_REGISTRATION_ERROR";
+
+ private GCMConstants() {
+ throw new UnsupportedOperationException();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * 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.google.android.gcm;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * Utilities for device registration.
+ * <p>
+ * <strong>Note:</strong> this class uses a private {@link SharedPreferences}
+ * object to keep track of the registration token.
+ */
+public final class GCMRegistrar {
+
+ /**
+ * Default lifespan (7 days) of the {@link #isRegisteredOnServer(Context)}
+ * flag until it is considered expired.
+ */
+ // NOTE: cannot use TimeUnit.DAYS because it's not available on API Level 8
+ public static final long DEFAULT_ON_SERVER_LIFESPAN_MS =
+ 1000 * 3600 * 24 * 7;
+
+ private static final String TAG = "GCMRegistrar";
+ private static final String BACKOFF_MS = "backoff_ms";
+ private static final String GSF_PACKAGE = "com.google.android.gsf";
+ private static final String PREFERENCES = "com.google.android.gcm";
+ private static final int DEFAULT_BACKOFF_MS = 3000;
+ private static final String PROPERTY_REG_ID = "regId";
+ private static final String PROPERTY_APP_VERSION = "appVersion";
+ /**
+ * {@link GCMBroadcastReceiver} instance used to handle the retry intent.
+ *
+ * <p>
+ * This instance cannot be the same as the one defined in the manifest
+ * because it needs a different permission.
+ */
+ private static GCMBroadcastReceiver sRetryReceiver;
+
+ private static String sRetryReceiverClassName;
+
+ /**
+ * Checks if the device has the proper dependencies installed.
+ * <p>
+ * This method should be called when the application starts to verify that
+ * the device supports GCM.
+ *
+ * @param context application context.
+ * @throws UnsupportedOperationException if the device does not support GCM.
+ */
+ public static void checkDevice(final Context context) {
+ final int version = Build.VERSION.SDK_INT;
+ if (version < 8) {
+ throw new UnsupportedOperationException("Device must be at least " +
+ "API Level 8 (instead of " + version + ")");
+ }
+ final PackageManager packageManager = context.getPackageManager();
+ try {
+ packageManager.getPackageInfo(GSF_PACKAGE, 0);
+ } catch (NameNotFoundException e) {
+ throw new UnsupportedOperationException(
+ "Device does not have package " + GSF_PACKAGE);
+ }
+ }
+
+ /**
+ * Initiate messaging registration for the current application.
+ * <p>
+ * The result will be returned as an
+ * {@link GCMConstants#INTENT_FROM_GCM_REGISTRATION_CALLBACK} intent with
+ * either a {@link GCMConstants#EXTRA_REGISTRATION_ID} or
+ * {@link GCMConstants#EXTRA_ERROR}.
+ *
+ * @param context application context.
+ * @param senderIds Google Project ID of the accounts authorized to send
+ * messages to this application.
+ * @throws IllegalStateException if device does not have all GCM
+ * dependencies installed.
+ */
+ public static void register(Context context, String... senderIds) {
+ GCMRegistrar.resetBackoff(context);
+ internalRegister(context, senderIds);
+ }
+
+ static void internalRegister(Context context, String... senderIds) {
+ String flatSenderIds = getFlatSenderIds(senderIds);
+ Log.v(TAG, "Registering app " + context.getPackageName() +
+ " of senders " + flatSenderIds);
+ Intent intent = new Intent(GCMConstants.INTENT_TO_GCM_REGISTRATION);
+ intent.setPackage(GSF_PACKAGE);
+ intent.putExtra(GCMConstants.EXTRA_APPLICATION_PENDING_INTENT,
+ PendingIntent.getBroadcast(context, 0, new Intent(), 0));
+ intent.putExtra(GCMConstants.EXTRA_SENDER, flatSenderIds);
+ context.startService(intent);
+ }
+
+ static String getFlatSenderIds(String... senderIds) {
+ if (senderIds == null || senderIds.length == 0) {
+ throw new IllegalArgumentException("No senderIds");
+ }
+ StringBuilder builder = new StringBuilder(senderIds[0]);
+ for (int i = 1; i < senderIds.length; i++) {
+ builder.append(',').append(senderIds[i]);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Clear internal resources.
+ *
+ * <p>
+ * This method should be called by the main activity's {@code onDestroy()}
+ * method.
+ */
+ public static synchronized void onDestroy(Context context) {
+ if (sRetryReceiver != null) {
+ Log.v(TAG, "Unregistering receiver");
+ context.unregisterReceiver(sRetryReceiver);
+ sRetryReceiver = null;
+ }
+ }
+
+ static void internalUnregister(Context context) {
+ Log.v(TAG, "Unregistering app " + context.getPackageName());
+ Intent intent = new Intent(GCMConstants.INTENT_TO_GCM_UNREGISTRATION);
+ intent.setPackage(GSF_PACKAGE);
+ intent.putExtra(GCMConstants.EXTRA_APPLICATION_PENDING_INTENT,
+ PendingIntent.getBroadcast(context, 0, new Intent(), 0));
+ context.startService(intent);
+ }
+
+ /**
+ * Lazy initializes the {@link GCMBroadcastReceiver} instance.
+ */
+ static synchronized void setRetryBroadcastReceiver(Context context) {
+ if (sRetryReceiver == null) {
+ if (sRetryReceiverClassName == null) {
+ // should never happen
+ Log.e(TAG, "internal error: retry receiver class not set yet");
+ sRetryReceiver = new GCMBroadcastReceiver();
+ } else {
+ Class<?> clazz;
+ try {
+ clazz = Class.forName(sRetryReceiverClassName);
+ sRetryReceiver = (GCMBroadcastReceiver) clazz.newInstance();
+ } catch (Exception e) {
+ Log.e(TAG, "Could not create instance of " +
+ sRetryReceiverClassName + ". Using " +
+ GCMBroadcastReceiver.class.getName() +
+ " directly.");
+ sRetryReceiver = new GCMBroadcastReceiver();
+ }
+ }
+ String category = context.getPackageName();
+ IntentFilter filter = new IntentFilter(
+ GCMConstants.INTENT_FROM_GCM_LIBRARY_RETRY);
+ filter.addCategory(category);
+ // must use a permission that is defined on manifest for sure
+ String permission = category + ".permission.C2D_MESSAGE";
+ Log.v(TAG, "Registering receiver");
+ context.registerReceiver(sRetryReceiver, filter, permission, null);
+ }
+ }
+
+ /**
+ * Sets the name of the retry receiver class.
+ */
+ static void setRetryReceiverClassName(String className) {
+ Log.v(TAG, "Setting the name of retry receiver class to " + className);
+ sRetryReceiverClassName = className;
+ }
+
+ /**
+ * Gets the current registration id for application on GCM service.
+ * <p>
+ * If result is empty, the registration has failed.
+ *
+ * @return registration id, or empty string if the registration is not
+ * complete.
+ */
+ public static String getRegistrationId(Context context) {
+ final SharedPreferences prefs = getGCMPreferences(context);
+ String registrationId = prefs.getString(PROPERTY_REG_ID, "");
+ // check if app was updated; if so, it must clear registration id to
+ // avoid a race condition if GCM sends a message
+ int oldVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
+ int newVersion = getAppVersion(context);
+ if (oldVersion != Integer.MIN_VALUE && oldVersion != newVersion) {
+ Log.v(TAG, "App version changed from " + oldVersion + " to " +
+ newVersion + "; resetting registration id");
+ clearRegistrationId(context);
+ registrationId = "";
+ }
+ return registrationId;
+ }
+
+ /**
+ * Checks whether the application was successfully registered on GCM
+ * service.
+ */
+ static boolean isRegistered(Context context) {
+ return getRegistrationId(context).length() > 0;
+ }
+
+ /**
+ * Clears the registration id in the persistence store.
+ *
+ * @param context application's context.
+ * @return old registration id.
+ */
+ static String clearRegistrationId(Context context) {
+ return setRegistrationId(context, "");
+ }
+
+ /**
+ * Sets the registration id in the persistence store.
+ *
+ * @param context application's context.
+ * @param regId registration id
+ */
+ static String setRegistrationId(Context context, String regId) {
+ final SharedPreferences prefs = getGCMPreferences(context);
+ String oldRegistrationId = prefs.getString(PROPERTY_REG_ID, "");
+ int appVersion = getAppVersion(context);
+ Log.v(TAG, "Saving regId on app version " + appVersion);
+ Editor editor = prefs.edit();
+ editor.putString(PROPERTY_REG_ID, regId);
+ editor.putInt(PROPERTY_APP_VERSION, appVersion);
+ editor.commit();
+ return oldRegistrationId;
+ }
+
+ /**
+ * Gets the application version.
+ */
+ private static int getAppVersion(Context context) {
+ try {
+ PackageInfo packageInfo = context.getPackageManager()
+ .getPackageInfo(context.getPackageName(), 0);
+ return packageInfo.versionCode;
+ } catch (NameNotFoundException e) {
+ // should never happen
+ throw new RuntimeException("Coult not get package name: " + e);
+ }
+ }
+
+ /**
+ * Resets the backoff counter.
+ * <p>
+ * This method should be called after a GCM call succeeds.
+ *
+ * @param context application's context.
+ */
+ static void resetBackoff(Context context) {
+ Log.d(TAG, "resetting backoff for " + context.getPackageName());
+ setBackoff(context, DEFAULT_BACKOFF_MS);
+ }
+
+ /**
+ * Gets the current backoff counter.
+ *
+ * @param context application's context.
+ * @return current backoff counter, in milliseconds.
+ */
+ static int getBackoff(Context context) {
+ final SharedPreferences prefs = getGCMPreferences(context);
+ return prefs.getInt(BACKOFF_MS, DEFAULT_BACKOFF_MS);
+ }
+
+ /**
+ * Sets the backoff counter.
+ * <p>
+ * This method should be called after a GCM call fails, passing an
+ * exponential value.
+ *
+ * @param context application's context.
+ * @param backoff new backoff counter, in milliseconds.
+ */
+ static void setBackoff(Context context, int backoff) {
+ final SharedPreferences prefs = getGCMPreferences(context);
+ Editor editor = prefs.edit();
+ editor.putInt(BACKOFF_MS, backoff);
+ editor.commit();
+ }
+
+ private static SharedPreferences getGCMPreferences(Context context) {
+ return context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
+ }
+
+ private GCMRegistrar() {
+ throw new UnsupportedOperationException();
+ }
+}
--- /dev/null
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+-dontobfuscate
+-libraryjars org.eclipse.jdt.annotation_1.0.0.dist.jar
+# Add any project specific keep options here:
+
+# 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 *;
+#}
--- /dev/null
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" >
+
+ <TextView
+ android:id="@+id/consoleTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:freezesText="true"
+ android:textIsSelectable="true" />
+
+ </ScrollView>
+
+ <EditText
+ android:id="@+id/argEditText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:imeOptions="actionSend|flagForceAscii"
+ android:inputType="text"
+ android:singleLine="true" >
+ <requestFocus />
+ </EditText>
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/resultTextView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textIsSelectable="false" />
+
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ </ListView>
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/ongoingItemTextView"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textIsSelectable="false" />
+
+ <Button
+ android:id="@+id/ongoingItemButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/cancel" />
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:id="@+id/prefsItem" android:title="@string/menu_preferences_title" android:titleCondensed="Prefs" android:showAsAction="ifRoom" android:icon="@android:drawable/ic_menu_preferences"></item>
+ <item android:id="@+id/localItem" android:showAsAction="ifRoom" android:title="@string/menu_local_commands_title" android:titleCondensed="LOCAL" android:icon="@android:drawable/ic_menu_more"></item>
+ <item android:id="@+id/helpItem" android:title="@string/menu_help_title" android:showAsAction="ifRoom" android:icon="@android:drawable/ic_menu_help"></item>
+
+
+</menu>
\ No newline at end of file
--- /dev/null
+<resources>
+ <string name="app_name">FonBot</string>
+ <string name="send_message_permission_desc">Allows an application to send notifications through FonBot</string>
+ <string name="help">Initial setup:\n
+<ol>
+ <li><a href="http://auth.ieval.ro">Register an account</a>\n</li>
+ <li>Enter your username and password in the <a href="ro.ieval.fonbot.FonBotPreferenceActivity://">preferences</a>\n\n</li>
+</ol>
+
+Sending commands:\n
+<ul>
+ <li>via Jabber: Send commands to fonbot@ieval.ro from any Jabber/GTalk account\n</li>
+ <li>via Yahoo Messenger: Send commands to fonbotym\n</li>
+ <li>via SMS: Set a SMS Password in the <a href="ro.ieval.fonbot.FonBotPreferenceActivity://">preferences</a>, then send SMSes with the SMS Password as the first line. The next lines will be processed as commands\n</li>
+ <li>via email: Send commands to fonbot@ieval.ro\n</li>
+</ul>
+\nFonBot commands consist of a command name and zero or more arguments, separated by spaces. To include spaces in an argument, surround the argument with single or double quotes.\n\n
+Example command: sms 0755555555 \'This is a random text\'. This command would send a text message to 0755555555\n\n
+The help command can be used to get a list of commands and help for them. Examples:\n\n
+<ul>
+ <li>help — shows a list of commands\n</li>
+ <li>help sms — shows help for the ls comand</li>
+</ul>
+</string>
+ <string name="user_tapped_fmt">User tapped \'%s\'</string>
+ <string name="user_navigated_away_from_dialog">User navigated away from dialog</string>
+ <string name="user_canceled_dialog">User canceled dialog</string>
+ <string name="admin_disable_requested">Admin disable requested</string>
+ <string name="admin_enabled">Admin enabled</string>
+ <string name="device_password_changed">Device password changed</string>
+ <string name="device_login_failed_fmt">Device login failed. Wrong password entered %s times</string>
+ <string name="device_login_succeeded">Device login succeeded</string>
+ <string name="logging_in">Logging in…</string>
+ <string name="phone_status_idle">Phone is now idle</string>
+ <string name="phone_status_ringing">"Phone is ringing. Incoming number: "</string>
+ <string name="phone_status_offhook">Phone is offhook</string>
+ <string name="location">Location</string>
+ <string name="latitude">Latitude</string>
+ <string name="longitude">Longitude</string>
+ <string name="accuracy">Accuracy</string>
+ <string name="altitude">Altitude</string>
+ <string name="bearing">Bearing</string>
+ <string name="speed">Speed</string>
+ <string name="at">at</string>
+ <string name="location_provider_disabled">Location provider %s disabled</string>
+ <string name="location_provider_enabled">Location provider %s enabled</string>
+ <string name="location_provider_available">Location provider %s is now available</string>
+ <string name="location_provider_temporary_unavailable">Location provider %s is temporary unavailable</string>
+ <string name="location_provider_out_of_service">Location provider %s is out of service</string>
+ <string name="location_provider_unknown_state">Location provider %s is in an unknown state</string>
+ <string name="no_response_returned_from_server">No response returned from server</string>
+ <string name="connection_error">Connection error</string>
+ <string name="sms_received_fmt">SMS received from %1$s with text %2$s</string>
+ <string name="battery_low">Battery low</string>
+ <string name="battery_okay">Battery okay</string>
+ <string name="plugged_in">plugged in</string>
+ <string name="unplugged">unplugged</string>
+ <string name="headset_with_microphone">Headset (with a microphone)</string>
+ <string name="headset_without_microphone">Headset (without a microphone)</string>
+ <string name="help_activity_label">FonBot Help</string>
+ <string name="foreground_notification_text">FonBot is running</string>
+ <string name="foreground_notification_title">FonBot</string>
+ <string name="phone_numer_type_assistant">Assistant</string>
+ <string name="phone_number_type_callback">Callback</string>
+ <string name="phone_number_type_car">Car</string>
+ <string name="phone_number_type_company_main">Company main</string>
+ <string name="phone_number_type_home_fax">Home fax</string>
+ <string name="phone_number_type_work_fax">Work fax</string>
+ <string name="phone_number_type_home">Home</string>
+ <string name="phone_number_type_isdn">ISDN</string>
+ <string name="phone_number_type_main">Main</string>
+ <string name="phone_number_type_mms">MMS</string>
+ <string name="phone_number_type_mobile">Mobile</string>
+ <string name="phone_number_type_other">Other</string>
+ <string name="phone_number_type_other_fax">Other fax</string>
+ <string name="phone_number_type_pager">Pager</string>
+ <string name="phone_number_type_radio">Radio</string>
+ <string name="phone_number_type_telex">TELEX</string>
+ <string name="phone_number_type_textphone">Textphone</string>
+ <string name="phone_number_type_work">Work</string>
+ <string name="phone_number_type_work_mobile">Work mobile</string>
+ <string name="phone_number_type_work_pager">Work pager</string>
+ <string name="phone_number_type_unknown">Unknown (%d)</string>
+ <string name="ringing">Ringing…</string>
+ <string name="cannot_grab_camera">Cannot grab camera</string>
+ <string name="no_longer_ringing">No longer ringing</string>
+ <string name="no_bluetooth_adapter">No bluetooth adapter</string>
+ <string name="bluetooth_on">Bluetooth: on</string>
+ <string name="bluetooth_off">Bluetooth: off</string>
+ <string name="enabling_bluetooth">Enabling bluetooth…</string>
+ <string name="disabling_bluetooth">Disabling bluetooth…</string>
+ <string name="incoming_call_from">"Incoming call from %s "</string>
+ <string name="missed_call_from">"Missed call from %s "</string>
+ <string name="outgoing_call_to">"Outgoing call to %s "</string>
+ <string name="duration_seconds_starting_at">Duration: %1$d seconds starting at %2$s</string>
+ <string name="no_matching_contacts_found">No matching contacts found</string>
+ <string name="contact_info">Name: %1$s; Phone: %2$s; Type: %3$s</string>
+ <string name="battery_level">"Battery level: %f%%. "</string>
+ <string name="not_plugged_in">"Not plugged in. "</string>
+ <string name="plugged_in_ac">"Plugged in (AC). "</string>
+ <string name="plugged_in_usb">"Plugged in (USB). "</string>
+ <string name="plugged_in_wireless">"Plugged in (wireless). "</string>
+ <string name="status_charging">"Status: charging. "</string>
+ <string name="status_discharging">"Status: discharging. "</string>
+ <string name="status_full">"Status: full. "</string>
+ <string name="status_not_charging">"Status: not charging. "</string>
+ <string name="status_unknown">Status: unknown. </string>
+ <string name="temperature">"Temperature: %d. "</string>
+ <string name="voltage">Voltage: %d.</string>
+ <string name="dialing">Dialing %s…</string>
+ <string name="menu_preferences_title">Preferences</string>
+ <string name="menu_local_commands_title">Local commands</string>
+ <string name="menu_help_title">Help</string>
+ <string name="showing_dialog">Showing dialog…</string>
+ <string name="last_known_location">"Last known location: "</string>
+ <string name="listening_for_location_updates">Listening for location updates…</string>
+ <string name="device_locked">Device locked</string>
+ <string name="file_not_found">File \'%s\' not found</string>
+ <string name="file_sent">File sent</string>
+ <string name="cannot_connect_to_host_on_port">Cannot connect to host \'%1$s\' on port \'%2$d\'</string>
+ <string name="io_error">I/O Error: %s</string>
+ <string name="no_longer_listening_for_location_updates">No longer listening for location updates</string>
+ <string name="error_setting_preview_display">Error setting preview display</string>
+ <string name="no_help_for_this_command">No help for this command</string>
+ <string name="string_is_not_a_directory">%s is not a directory</string>
+ <string name="files_in_directory">"Files in %s: "</string>
+ <string name="no_ringtone_found">No ringtone found</string>
+ <string name="ringer_mode_normal">Ringer mode: normal</string>
+ <string name="ringer_mode_vibrate">Ringer mode: vibrate</string>
+ <string name="ringer_mode_silent">Ringer mode: silent</string>
+ <string name="unknown_ringer_mode">Unknown ringer mode</string>
+ <string name="file_deleted">File deleted</string>
+ <string name="error_while_deleting_file">Error while deleting file</string>
+ <string name="password_cleared">Password cleared</string>
+ <string name="password_set">Password set</string>
+ <string name="message_was_split_into_parts">Message was split into %d parts</string>
+ <string name="tts_engine_not_available">TTS Engine not available</string>
+ <string name="speaking">Speaking…</string>
+ <string name="toast_shown">Toast shown</string>
+ <string name="vibrating">Vibrating…</string>
+ <string name="url_opened">URL opened</string>
+ <string name="no_activity_found_for_this_url">No activity found for this URL</string>
+ <string name="invalid_url">Invalid URL</string>
+ <string name="wifi_on">Wifi: on</string>
+ <string name="wifi_off">Wifi: off</string>
+ <string name="enabling_wifi">Enabling wifi…</string>
+ <string name="disabling_wifi">Disabling wifi…</string>
+ <string name="polling_server">Polling server…</string>
+ <string name="polling_stopped">Polling stopped</string>
+ <string name="command_disabled">Command disabled%s disabled</string>
+ <string name="command_enabled">Command %s enabled</string>
+ <string name="polling_every_milliseconds">Polling every %d milliseconds</string>
+ <string name="incoming_message">Incoming message from %1$s: \"%2$s\" sent at %3$s</string>
+ <string name="outgoing_message">Outgoing message to %1$s: \"%2$s\" sent at %3$s</string>
+ <string name="error_writing_to_socket">Error writing to socket: %s</string>
+ <string name="cannot_close_socket">Cannot close socket: %s</string>
+ <string name="unknown_host">Unknown host: %s</string>
+ <string name="photo_sent">Photo sent</string>
+ <string name="sending_photo">Sending photo…</string>
+ <string name="invalid_length_allowed_values_are">"Invalid length. Allowed values are: %s"</string>
+ <string name="could_not_parse_argument_allowed_values_are">Could not parse argument. Allowed values are: %s</string>
+ <string name="exception_while_hanging_up_call">Exception while hanging up call %1$s: %2$s</string>
+ <string name="exception_while_answering_call">Exception while answering call %1$s: %2$s</string>
+ <string name="no_such_package">No such package</string>
+ <string name="app_launched">App launched</string>
+ <string name="exception_while_getting_itelephony">Exception while getting ITelephony %1$s: %2$s</string>
+ <string name="data_off">Data: off</string>
+ <string name="data_on">Data: on</string>
+ <string name="disabling_data">Disabling data…</string>
+ <string name="enabling_data">Enabling data…</string>
+ <string name="exception_while_determining_data_state">Exception while determining data state</string>
+ <string name="enabling_gps">Enabling GPS…</string>
+ <string name="disabling_gps">Disabling GPS…</string>
+ <string name="enabling_network_location">Enabling network location…</string>
+ <string name="disabling_network_location">Disabling network location…</string>
+ <string name="gps_on">GPS: on</string>
+ <string name="gps_off">GPS: off</string>
+ <string name="network_location_off">Network location: off</string>
+ <string name="network_location_on">Network location: on</string>
+ <string name="answer_help">
+ Usage: answer\n
+ Answers the phone, if it is ringing. Does nothing otherwise.\n
+ Example: answer
+ </string>
+ <string name="batt_help">
+ Usage: batt\n
+ Shows battery information\n
+ Example: batt
+ </string>
+ <string name="wipe_help">
+ Usage: wipe type \"%s\"\n
+ If <type> is \"data\", wipes the phone, leaving the sdcard intact\n
+ If <type> is \"full\", wipes the phone and the sdcard\n
+ This command requires admin permissions\n
+ WARNING: THIS CANNOT BE UNDONE!\n
+ Example: wipe data
+ </string>
+ <string name="poll_help">
+ Usage: poll [ms]\n
+ With no arguments, polls the server once\n
+ With an argument, starts polling the server every <ms> milliseconds. If <ms> is 0, the polling is stopped\n
+ Example: poll 60000
+ </string>
+ <string name="contacts_help">
+ Usage: contacts substring\n
+ Returns a list of contacts whose name or nickname contains <substring>\n
+ Example: contacts Jack
+ </string>
+ <string name="rm_help">
+ Usage: rm file\n
+ Removes the <file> file\n
+ Example: rm /mnt/sdcard/file
+ </string>
+ <string name="ls_help">
+ Usage: ls directory\n
+ Lists the contents of the <directory> directory\n
+ Example: ls /mnt/sdcard
+ </string>
+ <string name="sms_help">
+ Usage: sms phone_number message\n
+ Sends an sms with text <message> to <phone_number>.\n
+ Example: sms 0755555555 \"Test message\"
+ </string>
+ <string name="setpassword_help">
+ Usage: setpassword [password]\n
+ With no argument, clears the device password.\n
+ With an argument, sets the device password to <password>.\n
+ Example: setpassword verysecurepassword
+ </string>
+ <string name="ring_help">
+ Usage: ring on/off\n
+ Make phone start/stop ringing\n
+ Example: ring on
+ </string>
+ <string name="photo_help">
+ Usage: photo hostname port\n
+ Takes a photo and uploads it to <hostname>:<port>\n
+ Example: photo 1.1.1.1 8888
+ </string>
+ <string name="nolocation_help">
+ Usage: nolocation\n
+ Stops sending location updates.\n
+ Example: nolocation
+ </string>
+ <string name="ncfile_help">
+ Usage: ncfile filename hostname port\n
+ Uploads the <filename> file to <hostname>:<port>\n
+ Example: ncfile /mnt/sdcard/file 1.1.1.1 8888
+ </string>
+ <string name="lock_help">
+ Usage: lock\n
+ Locks the phone. Requires device admin.\n
+ Example: lock
+ </string>
+ <string name="flash_help">
+ Usage: flash on/off\n
+ Turns the flashlight on or off\n
+ Example: flash on
+ </string>
+ <string name="echo_help">
+ Usage: echo message …\n
+ Echoes <message>.\n
+ Example: echo Hello, world!
+ </string>
+ <string name="dial_help">
+ Usage: dial number\n
+ Dials <number>.\n
+ Example: dial 0755555555
+ </string>
+ <string name="wifi_help">
+ Usage: wifi [on/off]\n
+ With no arguments, shows the wifi status\n
+ With an argument, turns the wifi on or off\n
+ Example: wifi on
+ </string>
+ <string name="view_help">
+ Usage: view link\n
+ Opens the link <link> in an appropriate application\n
+ Example: view tel://0755555555
+ </string>
+ <string name="vibrate_help">
+ Usage: vibrate ms\n
+ Makes the phone vibrate for <ms> milliseconds\n
+ Example: vibrate 2500
+ </string>
+ <string name="toast_help">
+ Usage: toast text [length]\n
+ Shows a toast with the <text> message. <length> is the length of the toast (long or short). Default is short.\n
+ Example: toast \"Hello world!\" long
+ </string>
+ <string name="speak_help">
+ Usage: speak text …\n
+ Speaks <text>\n
+ Example: speak Hello, stranger!
+ </string>
+ <string name="smslog_help">
+ Usage: smslog [count]\n
+ With no argument, shows the last 5 SMSes\n
+ With an argument, shows the last <count> SMSes\n
+ Example: smslog 10
+ </string>
+ <string name="play_help">
+ Usage: play\n
+ Starts playing music\n
+ Example: play
+ </string>
+ <string name="pause_help">
+ Usage: pause\n
+ Stops playing music\n
+ Example: pause
+ </string>
+ <string name="next_help">
+ Usage: next\n
+ Skips to the next track\n
+ Example: next
+ </string>
+ <string name="prev_help">
+ Usage: prev\n
+ Skips to the previous track\n
+ Example: prev
+ </string>
+ <string name="bluetooth_help">
+ Usage: bluetooth [on/off]\n
+ With no arguments, prints the bluetooth status.\n
+ With an argument, turns bluetooth on or off.\n
+ Example: bluetooth on
+ </string>
+ <string name="calllog_help">
+ Usage: calllog [count]\n
+ With no arguments, prints the last 5 calls.\n
+ With an argument, prints the last <count> calls\n
+ Example: calllog 10
+ </string>
+ <string name="dialog_help">
+ Usage: dialog message [button] …\n
+ Shows a dialog box with the <message> text and the <button…> buttons. Returns the button pressed\n
+ Example: dialog \"Hello stranger. Thanks for finding my phone, will you return it to me?\" Yes No
+ </string>
+ <string name="enable_help">
+ Usage: enable command\n
+ Enables a command, undoing the effects of a previous DISABLE command\n
+ This command can only be issued from the local command interface\n
+ Command list: %s\n
+ Example: disable toast
+ </string>
+ <string name="disable_help">
+ Usage: disable command\n" +
+ Disables a command. Once disabled, a command cannot be used until enabled with the ENABLE command\n
+ This command can only be issued from the local command interface\n
+ Command list: %s\n
+ Example: disable toast
+ </string>
+ <string name="help_help">
+ Usage: help command\n
+ Prints help for <command>. Available commands are %s\n
+ Example: help batt
+ </string>
+ <string name="setnotification_help">
+ Usage: setnotification notification\n
+ Enables the <notification> notification.\n
+ Messages from this notification will be sent to this address.\n
+ Available notifications: %s\n
+ Example: setnotification sms
+ </string>
+ <string name="delnotification_help">
+ Usage: delnotification notification\n
+ Disables the <notification> notification.\n
+ Available notifications: %s\n
+ Example: delnotification sms
+ </string>
+ <string name="location_help">
+ Usage: location provider [min_time [min_distance]]\n" +
+ Starts sending location updates. Location updates will be sent at most once each min_time (default: 500) milliseconds and only if the phone has moved at least min_distance meters (default: 0) from the last update\n
+ <provider> is the location provider, one of %s\n
+ Example: location network 0 0"
+ </string>
+ <string name="ringer_help">
+ Usage: ringer mode\n
+ Sets the ringer mode. <mode> is one of: %s\n
+ Example: ringer vibrate
+ </string>
+ <string name="data_help">
+ Usage: data [on/off]\n
+ With no arguments, shows the mobile data status\n
+ With an argument, turns the mobile data on or off\n
+ Example: data off
+ </string>
+ <string name="gps_help">
+ Usage: gps [on/off]\n
+ With no arguments, shows the GPS status\n
+ With an argument, turns the GPS on or off\n
+ Example: gps off
+ </string>
+ <string name="glocation_help">
+ Usage: glocation [on/off]\n
+ With no arguments, shows the Google location service status\n
+ With an argument, turns the Google location service on or off\n
+ NOTE: turning the Google location service on remotely is not possible, since the user must agree to the service disclaimer.\n
+ Example: glocation off
+ </string>
+ <string name="hangup_help">
+ Usage: hangup\n
+ Hangs the phone up.\n
+ Example: hangup
+ </string>
+ <string name="launch_help">
+ Usage: launch package\n
+ Launches the app defined by the <package> package\n
+ Example: launch ro.ieval.fonbot
+ </string>
+ <string name="could_not_parse_ms">Could not parse ms</string>
+ <string name="cannot_parse_provider_allowed_values_are">Cannot parse provider. Allowed values are: %s</string>
+ <string name="cannot_parse_min_time">Cannot parse min_time</string>
+ <string name="cannot_parse_min_distance">Cannot parse min_distance</string>
+ <string name="invalid_ringer_mode_valid_values_are">Invalid ringer mode. Valid values are: %s</string>
+ <string name="cannot_parse_port">Cannot parse port</string>
+ <string name="notification_enabled">Notification enabled</string>
+ <string name="messagetype_should_be_one_of">MessageType should be one of: %s</string>
+ <string name="notification_disabled">Notification disabled</string>
+ <string name="security_exception">Security exception: %s</string>
+ <string name="the_second_argument_to_wipe_must_be">The second argument to wipe must be \"%s\"</string>
+ <string name="wipetype_should_be_one_of">WipeType should be one of: %s</string>
+ <string name="cannot_parse_count">Cannot parse count</string>
+ <string name="no_such_command_command_list">No such command. Command list: %s</string>
+ <string name="cannot_parse_interval">Cannot parse interval</string>
+ <string name="unknown_command">Unknown command: \'%1$s\' (%2$s)</string>
+ <string name="error_while_processing_command">Error while processing command (%1$s: %2$s)</string>
+ <string name="rebooting">Rebooting…</string>
+ <string name="reboot_help">
+ Usage: reboot\n
+ Reboots the phone.\n
+ Example: reboot
+ </string>
+ <string name="shutdown_help">
+ Usage: shutdown\n
+ Shuts the phone down.\n
+ Example: shutdown
+ </string>
+ <string name="location_tracking_is_active">Location tracking is active</string>
+ <string name="the_polling_service_is_running">The polling service is running</string>
+ <string name="cancel">Cancel</string>
+ <string name="command_sent">Command sent</string>
+ <string name="could_not_parse_id">Could not parse id</string>
+ <string name="notification_canceled">Notification canceled</string>
+ <string name="notification_shown">Notification shown</string>
+ <string name="notify_help">Notify help</string>
+</resources>
\ No newline at end of file
--- /dev/null
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-policies>
+ <watch-login />
+ <reset-password />
+ <force-lock />
+ <wipe-data />
+ </uses-policies>
+</device-admin>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ <EditTextPreference android:title="Username" android:key="username" android:dialogTitle="Username" android:dialogMessage="Enter your username"/>
+ <EditTextPreference android:dialogTitle="Password" android:dialogMessage="Enter your password" android:inputType="textPassword" android:title="Password" android:key="password"/>
+ <EditTextPreference android:dialogTitle="SMS Password" android:dialogMessage="Enter the password required to send SMS commands. Leave empty to disable SMS commands" android:title="SMS Password" android:key="smspassword"/>
+ <CheckBoxPreference android:title="Device administration" android:summaryOn="Device administration is enabled." android:key="admin" android:summaryOff="Device administration is disabled. Some commands require device administration."/><CheckBoxPreference android:key="foreground" android:title="Run service in foreground" android:summaryOn="The FonBot service is kept in foreground. " android:summaryOff="The FonBot service is not kept in foreground."/><CheckBoxPreference android:key="poll_on_screen_on" android:summaryOff="The server is not polled when the screen is turned on" android:summaryOn="The server is polled when the screen is turned on" android:title="Poll server on screen on"/>
+ <CheckBoxPreference android:key="system" android:summaryOff="FonBot is not a system app. Changing this requires root and busybox" android:title="System App" android:summaryOn="FonBot is a system app. Changing this requires root and busybox"/>
+
+</PreferenceScreen>
\ No newline at end of file
--- /dev/null
+package com.android.internal.telephony;
+
+import android.os.Bundle;
+interface ITelephony {
+ boolean endCall();
+ void dial(String number);
+ void answerRingingCall();
+ boolean enableDataConnectivity();
+ boolean disableDataConnectivity();
+ int getDataState();
+}
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 Stephan Herrmann and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - initial API and implementation
+ * IBM Corporation - bug fixes
+ *******************************************************************************/
+package org.eclipse.jdt.annotation;
+
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Qualifier for a type in a method signature or a local variable declaration:
+ * The entity (return value, parameter, local variable) whose type has this
+ * annotation can never have the value <code>null</code> at runtime.
+ * <p>
+ * This has two consequences:
+ * <ol>
+ * <li>Dereferencing the entity is safe, i.e., no <code>NullPointerException</code> can occur at runtime.</li>
+ * <li>An attempt to bind a <code>null</code> value to the entity is a compile time error.</li>
+ * </ol>
+ * For the second case, diagnostics issued by the compiler should distinguish three situations:
+ * <ol>
+ * <li>Nullness of the value can be statically determined, the entity is definitely bound from either of:
+ * <ul><li>the value <code>null</code>, or</li>
+ * <li>an entity with a {@link Nullable @Nullable} type.</li></ul></li>
+ * <li>Nullness cannot definitely be determined, because different code branches yield different results.</li>
+ * <li>Nullness cannot be determined, because other program elements are involved for which
+ * null annotations are lacking.</li>
+ * </ol>
+ * </p>
+ * @since 1.0
+ */
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ METHOD, PARAMETER, LOCAL_VARIABLE })
+public @interface NonNull {
+ // marker annotation with no members
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2011 Stephan Herrmann and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - initial API and implementation
+ * IBM Corporation - bug fixes
+ *******************************************************************************/
+package org.eclipse.jdt.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be applied to a package, type, method or constructor in order to
+ * define that all contained entities for which a null annotation is otherwise lacking
+ * should be considered as {@link NonNull @NonNull}.
+ * <dl>
+ * <dt>Canceling a default</dt>
+ * <dd>By using a <code>@NonNullByDefault</code> annotation with the argument <code>false</code>,
+ * a default from any enclosing scope can be canceled for the element being annotated.
+ * <dt>Nested defaults</dt>
+ * <dd>If a <code>@NonNullByDefault</code>
+ * annotation is used within the scope of another <code>@NonNullByDefault</code>
+ * annotation or a project-wide default setting, the innermost annotation defines the
+ * default applicable at any given position (depending on the parameter {@link #value()}).</dd>
+ * </dl>
+ * Note that for applying an annotation to a package, a file by the name
+ * <code>package-info.java</code> is used.
+ *
+ * @since 1.0
+ */
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ PACKAGE, TYPE, METHOD, CONSTRUCTOR })
+public @interface NonNullByDefault {
+ /**
+ * When parameterized with <code>false</code>, the annotation specifies that the current element should not apply
+ * any default to un-annotated types.
+ */
+ boolean value() default true;
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 Stephan Herrmann and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - initial API and implementation
+ * IBM Corporation - bug fixes
+ *******************************************************************************/
+package org.eclipse.jdt.annotation;
+
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Qualifier for a type in a method signature or a local variable declaration:
+ * The entity (return value, parameter, local variable) whose type has this
+ * annotation is allowed to have the value <code>null</code> at runtime.
+ * <p>
+ * This has two consequences:
+ * <ul>
+ * <li>Binding a <code>null</code> value to the entity is legal.</li>
+ * <li>Dereferencing the entity is unsafe, i.e., a <code>NullPointerException</code> can occur at runtime.</li>
+ * </ul>
+ * </p>
+ * @since 1.0
+ */
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ METHOD, PARAMETER, LOCAL_VARIABLE })
+public @interface Nullable {
+ // marker annotation with no members
+}
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2011 Stephan Herrmann and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - initial API and implementation
+ * IBM Corporation - bug fixes
+ *******************************************************************************/
+
+/**
+ * This package contains annotations that can trigger special behavior
+ * when annotated types are compiled by the Eclipse Compiler for Java.
+ * <p>
+ * Currently, the package contains annotations that specify nullness contracts
+ * for annotated elements.
+ * </p>
+ */
+package org.eclipse.jdt.annotation;
+
--- /dev/null
+package ro.ieval.fonbot;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * An address to which messages can be sent using {@link Utils#sendMessage(android.content.Context, Address, int)}
+ *
+ * Addresses are defined by a protocol and a data part. The data is a protocol-dependent opaque String.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+final class Address {
+ /**
+ * The protocol of an Address.
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ public static enum Protocol{
+ /**
+ * The protocol used for sending messages via short text messages. The data for a SMS address is the phone number.
+ */
+ SMS,
+ /**
+ * The protocol used for sending messages via anything handled by the iEval server. The data is an opaque String supplied by the iEval server.
+ */
+ HTTP,
+ /**
+ * The protocol used for sending messages to {@link FonBotLocalActivity}
+ */
+ LOCAL,
+ /**
+ * The protocol used for suppressing messages.
+ */
+ NULL
+ }
+
+ /**
+ * An address for suppressing messages
+ */
+ public static final Address BLACKHOLE=new Address(Utils.toNonNull(Protocol.NULL), null);
+ /**
+ * The protocol part of the Address.
+ */
+ public final Protocol protocol;
+ /**
+ * The data part of the Address. Can be null
+ */
+ public final String data;
+
+ /**
+ * Construct an Address from its parts
+ *
+ * @param protocol the protocol part of the Address
+ * @param data the data part of the Address
+ */
+ public Address(final Protocol protocol, final @Nullable String data){
+ this.protocol=protocol;
+ this.data=data;
+ }
+
+ /**
+ * Construct an Address from its string representation (the protocol and data in this order separated by a single space character). Does the reverse of {@link #toString()}
+ *
+ * @param address the Address string representation
+ * @throws IllegalArgumentException if the protocol part is not a member of the {@link Protocol} enum
+ */
+ public Address(final String address) throws IllegalArgumentException{
+ final String[] parts=address.split(" ", 2);
+ this.protocol=Protocol.valueOf(parts[0]);
+ this.data=parts[1];
+ }
+
+ /**
+ * Returns the string representation of an Address, which is the protocol and data in this order separated by a single space character
+ */
+ @Override
+ public String toString(){
+ return protocol+" "+data;
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import android.app.backup.BackupAgentHelper;
+import android.app.backup.SharedPreferencesBackupHelper;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Backup agent. Backups the preferences file
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class BackupAgent extends BackupAgentHelper {
+ @Override
+ public void onCreate() {
+ addHelper("prefs", new SharedPreferencesBackupHelper(this,
+ getClass().getPackage().getName()+"_preferences"));
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * A dialog created by the {@link Utils.Command#DIALOG DIALOG} command. Includes a message and a list of buttons, all user-defined
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class DialogActivity extends Activity {
+ /** Extra: string array of dialog buttons */
+ public static final String EXTRA_BUTTONS = "buttons";
+ /** Extra: dialog message */
+ public static final String EXTRA_MESSAGE = "message";
+ /** Extra: reply Address */
+ public static final String EXTRA_REPLYTO = "replyto";
+
+ /** true if the dialog is finishing because the user tapped a button, false otherwise */
+ private boolean gotAnswer=false;
+ /** Reply address */
+ private Address address;
+
+ @Override
+ protected void onCreate(@Nullable final Bundle icicle) {
+ super.onCreate(icicle);
+ address=new Address(toNonNull(getIntent().getStringExtra(EXTRA_REPLYTO)));
+ final LinearLayout layout=new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ if(getIntent().hasExtra(EXTRA_MESSAGE)){
+ final String text=getIntent().getStringExtra(EXTRA_MESSAGE);
+ final TextView tv=new TextView(this);
+ tv.setText(text);
+ layout.addView(tv);
+ }
+ final String[] buttonLabels=getIntent().getStringArrayExtra(EXTRA_BUTTONS);
+
+ final OnClickListener buttonListener=new OnClickListener() {
+ @Override
+ public void onClick(@Nullable final View v) {
+ if(v==null)
+ return;
+
+ Utils.sendMessage(DialogActivity.this, toNonNull(address),
+ toNonNull(getString(user_tapped_fmt, ((Button)v).getText())));
+ gotAnswer=true;
+ finish();
+ }
+ };
+ for(final String label : buttonLabels){
+ final Button b=new Button(this);//NOPMD buttons must be different
+ b.setText(label);
+ b.setOnClickListener(buttonListener);
+ layout.addView(b);
+ }
+ setContentView(layout);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if(!gotAnswer){
+ Utils.sendMessage(this, toNonNull(address),
+ toNonNull(getString(user_navigated_away_from_dialog)));
+ gotAnswer=true;
+ }
+ finish();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if(!gotAnswer){
+ Utils.sendMessage(this, toNonNull(address),
+ toNonNull(getString(user_canceled_dialog)));
+ gotAnswer=true;
+ }
+ finish();
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.Utils.MessageType;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.preference.PreferenceManager;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * A BroadcastReceiver that receives "dynamic" events like {@link Intent#ACTION_SCREEN_ON ACTION_SCREEN_ON}, {@link Intent#ACTION_BATTERY_CHANGED ACTION_BATTERY_CHANGED} and {@link Intent#ACTION_HEADSET_PLUG}
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class DynamicEventReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(final @Nullable Context context, final @Nullable Intent intent) {
+ if(context==null || intent==null || isInitialStickyBroadcast())
+ return;
+
+ final String action=intent.getAction();
+
+ if(action.equals(Intent.ACTION_SCREEN_ON)){
+ if(PreferenceManager.getDefaultSharedPreferences(context).getBoolean("poll_on_screen_on", false))
+ new PollServerAsyncTask().execute();
+ } else if(action.equals(Intent.ACTION_BATTERY_CHANGED))
+ Heavy.describeBatteryLevel(context, null, toNonNull(MessageType.BATTERY_CHANGED));
+ else if(action.equals(Intent.ACTION_HEADSET_PLUG)){
+ final int state=intent.getIntExtra("state", 0);
+ final String name=intent.getStringExtra("name");
+ final int microphone=intent.getIntExtra("microphone", 0);
+
+ final StringBuilder builder=new StringBuilder(100);
+ if(microphone==1)
+ builder.append(toNonNull(context.getString(headset_with_microphone)));
+ else
+ builder.append(toNonNull(context.getString(headset_without_microphone)));
+ builder.append(' ');
+ builder.append(name);
+ builder.append(' ');
+ if(state==1)
+ builder.append(toNonNull(context.getString(plugged_in)));
+ else
+ builder.append(toNonNull(context.getString(unplugged)));
+ builder.append(". ");
+
+ Utils.sendMessage(context,
+ toNonNull(MessageType.HEADSET),
+ toNonNull(builder.toString()));
+ }
+ }
+
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.Utils.MessageType;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * The {@link DeviceAdminReceiver} used by FonBot. Sends {@link Utils.MessageType#ADMIN ADMIN} and {@link Utils.MessageType#WATCH_LOGIN WATCH_LOGIN} notifications.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class FonBotAdminReceiver extends DeviceAdminReceiver {
+ @Override
+ public void onDisabled(@Nullable final Context context, @Nullable final Intent intent) {
+ if(context==null)
+ return;
+ Utils.sendMessage(context, toNonNull(MessageType.ADMIN), "Admin disabled");
+ }
+
+ @Override
+ public @Nullable CharSequence onDisableRequested(@Nullable final Context context, @Nullable final Intent intent) {
+ if(context==null){
+ Log.wtf(getClass().getName(), "context is null in onDisableRequested");
+ throw new AssertionError("Log.wtf did not terminate the process");
+ }
+ Utils.sendMessage(context, toNonNull(MessageType.ADMIN),
+ toNonNull(context.getString(admin_disable_requested)));
+ return null;//TODO: write this
+ }
+
+ @Override
+ public void onEnabled(@Nullable final Context context, @Nullable final Intent intent) {
+ if(context==null)
+ return;
+ Utils.sendMessage(context, toNonNull(MessageType.ADMIN),
+ toNonNull(context.getString(admin_enabled)));
+ }
+
+ @Override
+ public void onPasswordChanged(@Nullable final Context context, @Nullable final Intent intent) {
+ if(context==null)
+ return;
+ Utils.sendMessage(context, toNonNull(MessageType.ADMIN),
+ toNonNull(context.getString(device_password_changed)));
+ }
+
+ @SuppressWarnings("boxing")
+ @Override
+ public void onPasswordFailed(@Nullable final Context context, @Nullable final Intent intent) {
+ if(context==null)
+ return;
+ final DevicePolicyManager dpm=(DevicePolicyManager) FonBotApplication.instance.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ Utils.sendMessage(context, toNonNull(MessageType.WATCH_LOGIN),
+ toNonNull(context.getString(device_login_failed_fmt, dpm.getCurrentFailedPasswordAttempts())));
+ }
+
+ @Override
+ public void onPasswordSucceeded(@Nullable final Context context, @Nullable final Intent intent) {
+ if(context==null)
+ return;
+ Utils.sendMessage(context, toNonNull(MessageType.WATCH_LOGIN), toNonNull(context.getString(device_login_succeeded)));
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import android.app.Application;
+import android.content.Intent;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import com.google.android.gcm.GCMRegistrar;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * The {@link Application} class of FonBot. Registers the device with GCM, starts the TTS service and keeps global state.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class FonBotApplication extends Application {
+ /**
+ * iEval GCM sender id
+ */
+ public static final String GCM_SENDER_ID = "379674287523";
+ /**
+ * The one instance of FonBotApplication
+ */
+ public static FonBotApplication instance=null;
+ /**
+ * The GCM registration ID
+ */
+ public String regID=null;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ //GCMRegistrar.checkDevice(this);
+ regID=GCMRegistrar.getRegistrationId(this);
+ if("".equals(regID))
+ GCMRegistrar.register(this, GCM_SENDER_ID);
+
+ final TelephonyManager tman=(TelephonyManager) getSystemService(TELEPHONY_SERVICE);
+ tman.listen(new FonBotPhoneStateListener(this), PhoneStateListener.LISTEN_CALL_STATE);
+ instance=this;
+
+ new PollServerAsyncTask().execute();
+
+ startService(new Intent(this, FonBotMainService.class));
+
+// Thread.setDefaultUncaughtExceptionHandler(new RemoteCrashdumpHandler(this));
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.text.method.LinkMovementMethod;
+import android.widget.TextView;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * An activity that shows FonBot help.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class FonBotHelpActivity extends Activity {
+ @Override
+ protected void onCreate(final @Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final TextView textView=new TextView(this);
+ textView.setText(help);
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
+ setTitle(help_activity_label);
+ setContentView(textView);
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+import static ro.ieval.fonbot.Address.Protocol.LOCAL;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+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.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Activity that lets users send commands from inside the app.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class FonBotLocalActivity extends Activity {
+ /**
+ * BroadcastReceiver that receives command responses.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ private final class ReplyReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(final @Nullable Context context, final @Nullable Intent intent) {
+ if (context == null || intent == null) return;
+ consoleTextView.setText(consoleTextView.getText() +
+ intent.getStringExtra(EXTRA_RESPONSE) + "\n");
+ }
+ }
+
+ /**
+ * The extra name for the command response.
+ *
+ * @see #RESPONSE_RECEIVED_ACTION
+ */
+ public static final String EXTRA_RESPONSE = "response";
+ /**
+ * The action for sending back responses to commands.
+ */
+ public static final String RESPONSE_RECEIVED_ACTION="ro.ieval.fonbot.LOCAL_RESPONSE";
+ /**
+ * Intent filter that catches {@link #RESPONSE_RECEIVED_ACTION}
+ */
+ private static final IntentFilter INTENT_FILTER = new IntentFilter(RESPONSE_RECEIVED_ACTION);
+ /**
+ * Reply address for local commands
+ */
+ private static final Address LOCAL_ADDRESS=new Address(toNonNull(LOCAL), null);
+ /**
+ * The one instance of {@link ReplyReceiver}
+ */
+ private final ReplyReceiver replyReceiver = new ReplyReceiver();
+ /**
+ * The TextView that contains command responses
+ */
+ private TextView consoleTextView;
+
+ @Override
+ protected void onCreate(final @Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.local);
+
+ consoleTextView = (TextView) findViewById(R.id.consoleTextView);
+ final EditText commandEditText = (EditText) findViewById(R.id.argEditText);
+ commandEditText.setOnEditorActionListener(new OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(final @Nullable TextView v, final int actionId, final @Nullable KeyEvent event) {
+ if(actionId==EditorInfo.IME_ACTION_SEND||
+ actionId==EditorInfo.IME_NULL){
+ final String[] commandWithArgs = Utils.shellwords(toNonNull(commandEditText.getText().toString()));
+ final String command=commandWithArgs[0];
+ final String[] args=new String[commandWithArgs.length-1];
+ System.arraycopy(commandWithArgs, 1, args, 0, args.length);
+ commandEditText.setText("");
+ Utils.processCommand(FonBotLocalActivity.this, toNonNull(command), args,
+ toNonNull(LOCAL_ADDRESS));
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ this.registerReceiver(replyReceiver, INTENT_FILTER);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ this.unregisterReceiver(replyReceiver);
+ }
+}
\ No newline at end of file
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import java.util.Arrays;
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.FonBotMainService.Binder;
+import ro.ieval.fonbot.Utils.OngoingEvent;
+
+import android.app.ListActivity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Main Activity. Shows the login status and a list of ongoing events.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class FonBotMainActivity extends ListActivity {
+ /**
+ * Adapter for the ongoing event list. Shows a list of ongoing events with cancel buttons.
+ *
+ * @author Marius Gavrilescu
+ */
+ private static final class ArrayAdapter extends android.widget.ArrayAdapter<OngoingEvent>{
+ /**
+ * Constructs an ArrayAdapter with a given list of events
+ *
+ * @param context Context instance
+ * @param objects the list of events
+ */
+ public ArrayAdapter(final Context context, final OngoingEvent[] objects) {
+ super(context, 0, 0, objects);
+ }
+
+ @Override
+ public View getView(final int position, final @Nullable View convertView, final @Nullable ViewGroup parent) {
+ final LayoutInflater inflater=(LayoutInflater) getContext().getSystemService(LAYOUT_INFLATER_SERVICE);
+ final View listItem;
+
+ if(convertView instanceof LinearLayout){
+ listItem=convertView;
+ } else
+ listItem=inflater.inflate(R.layout.ongoing_list_item, parent, false);
+
+ final Button button=(Button) listItem.findViewById(R.id.ongoingItemButton);
+ final TextView textView=(TextView) listItem.findViewById(R.id.ongoingItemTextView);
+ final OngoingEvent event=getItem(position);
+
+ textView.setText(event.resource);
+ button.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final @Nullable View v) {
+ Heavy.cancelOngoing(toNonNull(getContext()), event);
+ }
+ });
+
+ return listItem;
+ }
+ }
+
+ /**
+ * ServiceConnection for getting the ongoing event list from {@link FonBotMainService}
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ private final class ServiceConnection implements android.content.ServiceConnection{
+ /** Binder got from onServiceConnected */
+ private Binder binder;
+
+ @Override
+ public void onServiceDisconnected(final @Nullable ComponentName name) {
+ // do nothing
+ }
+
+ @Override
+ public void onServiceConnected(final @Nullable ComponentName name, final @Nullable IBinder service) {
+ if(service==null)
+ return;
+ binder=(Binder) service;
+ refreshAdapter();
+ }
+
+ /**
+ * Updates the list of ongoing events from the service.
+ */
+ public void refreshAdapter(){
+ if(binder==null)
+ return;
+ setListAdapter(new ArrayAdapter(FonBotMainActivity.this,
+ toNonNull(binder.getService().getOngoingEvents().toArray(new OngoingEvent[0]))));
+ }
+ }
+
+ /**
+ * BroadcastReceiver that refreshes the ongoing event list.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ private final class OngoingUpdateReceiver extends BroadcastReceiver{
+ @Override
+ public void onReceive(final @Nullable Context context, final @Nullable Intent intent) {
+ connection.refreshAdapter();
+ }
+ }
+
+ /**
+ * BroadcastReceiver that updates {@link #resultTextView}
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ private final class LoginReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(@Nullable final Context context, @Nullable final Intent intent) {
+ if(intent==null)
+ return;
+ resultTextView.setText(intent.getStringExtra(SendHttpMessageAsyncTask.EXTRA_RESPONSE_MESSAGE));
+ final int responseCode=intent.getIntExtra(SendHttpMessageAsyncTask.EXTRA_RESPONSE_CODE, 0);
+ if(responseCode>=200&&responseCode<300)
+ resultTextView.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.tick, 0, 0);
+ else
+ resultTextView.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.cross, 0, 0);
+ }
+ }
+
+ /**
+ * The one instance of {@link OngoingUpdateReceiver}
+ */
+ private final BroadcastReceiver ongoingUpdateReceiver=new OngoingUpdateReceiver();
+ /**
+ * IntentFilter for {@link #ongoingUpdateReceiver}
+ */
+ private static final IntentFilter ONGOING_UPDATE_FILTER=new IntentFilter(FonBotMainService.ACTION_ONGOING_UPDATE);
+ /**
+ * The one instance of {@link LoginReceiver}
+ */
+ private final BroadcastReceiver loginReceiver=new LoginReceiver();
+ /**
+ * The broadcast sent by {@link SendHttpMessageAsyncTask} when logging in.
+ */
+ private static final String LOGIN_BROADCAST="ro.ieval.fonbot.LOGIN_RESULT";
+ /**
+ * IntentFilter for {@link #loginReceiver}
+ */
+ private static final IntentFilter LOGIN_FILTER=new IntentFilter(LOGIN_BROADCAST);
+ /**
+ * The one instance of {@link ServiceConnection}
+ */
+ private final ServiceConnection connection=new ServiceConnection();
+
+ /**
+ * TextView that tells the user whether logging in failed or succeded.
+ */
+ private TextView resultTextView;
+
+ @Override
+ protected void onCreate(@Nullable final Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.main);
+
+ resultTextView=(TextView) findViewById(R.id.resultTextView);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ final SharedPreferences sp=PreferenceManager.getDefaultSharedPreferences(this);
+ resultTextView.setText(logging_in);
+ new SendHttpMessageAsyncTask(toNonNull(Arrays.asList(
+ new Header("X-Action","LOGIN")
+ )),LOGIN_BROADCAST,this).execute(
+ sp.getString("username", null),
+ sp.getString("password", null)
+ );
+ connection.refreshAdapter();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ registerReceiver(loginReceiver, LOGIN_FILTER);
+ LocalBroadcastManager.getInstance(this).registerReceiver(ongoingUpdateReceiver, ONGOING_UPDATE_FILTER);
+ bindService(new Intent(this, FonBotMainService.class), connection, 0);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(loginReceiver);
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(ongoingUpdateReceiver);
+ unbindService(connection);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(@Nullable final Menu menu) {
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@Nullable final MenuItem item) {
+ if(item==null)
+ return super.onOptionsItemSelected(item);
+ switch(item.getItemId()){
+ case R.id.prefsItem:
+ startActivity(new Intent(this, FonBotPreferenceActivity.class));
+ return true;
+ case R.id.localItem:
+ startActivity(new Intent(this, FonBotLocalActivity.class));
+ return true;
+ case R.id.helpItem:
+ startActivity(new Intent(this, FonBotHelpActivity.class));
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.Utils.OngoingEvent;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.content.LocalBroadcastManager;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Main service.
+ *
+ * @author Marius Gavrilescu
+ */
+public final class FonBotMainService extends Service {
+ /**
+ * Binder for the service. It lets clients get a reference to the service.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ public final class Binder extends android.os.Binder{
+ /**
+ * Get a reference to the {@link FonBotMainService}
+ * @return a reference to the {@link FonBotMainService}
+ */
+ public FonBotMainService getService(){
+ return FonBotMainService.this;
+ }
+ }
+
+ /**
+ * Broadcast action: add an ongoing event
+ */
+ public static final String ACTION_PUT_ONGOING="ro.ieval.fonbot.FonBotMainService.ACTION_PUT_ONGOING";
+ /**
+ * Broadcast action: remove an ongoing event
+ */
+ public static final String ACTION_DELETE_ONGOING="ro.ieval.fonbot.FonBotMainService.ACTION_DELETE_ONGOING";
+ /**
+ * Extra: ongoing event id
+ *
+ * @see OngoingEvent
+ * @see #ACTION_DELETE_ONGOING
+ * @see #ACTION_PUT_ONGOING
+ */
+ public static final String EXTRA_ONGOING_ID="ro.ieval.fonbot.FonBotMainService.EXTRA_ONGOING_ID";
+ /**
+ * Broadcast sent when the ongoing event list is updated.
+ */
+ public static final String ACTION_ONGOING_UPDATE="ro.ieval.fonbot.FonBotMainService.ACTION_ONGOING_UPDATE";
+ /**
+ * IntentFilter for events caught by the {@link DynamicEventReceiver}
+ */
+ private static final IntentFilter DYNAMIC_BROADCAST_FILTER=new IntentFilter();
+
+ static{
+ DYNAMIC_BROADCAST_FILTER.addAction(Intent.ACTION_SCREEN_ON);
+ DYNAMIC_BROADCAST_FILTER.addAction(Intent.ACTION_BATTERY_CHANGED);
+ DYNAMIC_BROADCAST_FILTER.addAction(Intent.ACTION_HEADSET_PLUG);
+ }
+
+ /**
+ * The one instance of {@link DynamicEventReceiver}
+ */
+ private final DynamicEventReceiver receiver=new DynamicEventReceiver();
+ /**
+ * Set of ongoing events.
+ */
+ private final Set<OngoingEvent> ongoing=new HashSet<OngoingEvent>(10);
+
+ /**
+ * Get the set of ongoing events.
+ *
+ * @return a set of ongoing events
+ */
+ public Set<OngoingEvent> getOngoingEvents(){
+ return toNonNull(Collections.unmodifiableSet(ongoing));
+ }
+
+ @Override
+ public @Nullable IBinder onBind(final @Nullable Intent intent) {
+ return new Binder();
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ registerReceiver(receiver, DYNAMIC_BROADCAST_FILTER);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(receiver);
+ }
+
+ @Override
+ public int onStartCommand(final @Nullable Intent intent, final int flags, final int startId) {
+ if(intent!=null && intent.getAction()==ACTION_PUT_ONGOING && intent.hasExtra(EXTRA_ONGOING_ID)){
+ ongoing.add(OngoingEvent.values()[intent.getIntExtra(EXTRA_ONGOING_ID, 0)]);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_ONGOING_UPDATE));
+ }
+ if(intent!=null && intent.getAction()==ACTION_DELETE_ONGOING && intent.hasExtra(EXTRA_ONGOING_ID)){
+ ongoing.remove(OngoingEvent.values()[intent.getIntExtra(EXTRA_ONGOING_ID, 0)]);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(ACTION_ONGOING_UPDATE));
+ }
+
+ final boolean runForeground=PreferenceManager.getDefaultSharedPreferences(this).getBoolean("foreground", false);
+ if(!runForeground)
+ stopForeground(true);
+
+ if(runForeground||!ongoing.isEmpty()){
+ final Intent mainIntent=new Intent(this, FonBotMainActivity.class);
+ mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final NotificationCompat.Builder builder=new NotificationCompat.Builder(this).
+ setContentText(getString(foreground_notification_text)).
+ setContentTitle(getString(foreground_notification_title)).
+ setSmallIcon(android.R.drawable.stat_notify_sync_noanim).
+ setPriority(NotificationCompat.PRIORITY_MIN).
+ setContentIntent(PendingIntent.getActivity(this, 0, mainIntent, 0)).
+ setOngoing(true);
+
+ final Notification notification;
+ if(ongoing.isEmpty())
+ notification=builder.build();
+ else {
+ final NotificationCompat.InboxStyle inboxBuilder=new NotificationCompat.InboxStyle(builder);
+
+ for(final OngoingEvent event : ongoing)
+ inboxBuilder.addLine(getString(event.resource));
+
+ notification=inboxBuilder.build();
+ }
+
+ if(runForeground)
+ startForeground(1337, builder.build());
+ else{
+ final NotificationManager man=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ man.notify(1337, notification);
+ }
+ }
+
+ return START_STICKY;
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.phone_status_idle;
+import static ro.ieval.fonbot.R.string.phone_status_offhook;
+import static ro.ieval.fonbot.R.string.phone_status_ringing;
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.Utils.MessageType;
+import android.content.Context;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * PhoneStateListener that sends {@link MessageType#PHONE_STATE} notifications.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+final class FonBotPhoneStateListener extends PhoneStateListener {
+ /**
+ * Context instance used in this class.
+ */
+ private final Context context;
+
+ /**
+ * Construct a FonBotPhoneStateListener with a given context.
+ *
+ * @param context Context instance
+ */
+ public FonBotPhoneStateListener(final Context context) {
+ super();
+ this.context=context;
+ }
+
+ @Override
+ public void onCallStateChanged(final int state, @Nullable final String incomingNumber) {
+ @SuppressWarnings("hiding")
+ final Context context=this.context;
+ if(context==null)
+ return;
+
+ switch(state){
+ case TelephonyManager.CALL_STATE_IDLE:
+ Utils.sendMessage(context, toNonNull(MessageType.PHONE_STATE),
+ toNonNull(context.getString(phone_status_idle)));
+ break;
+ case TelephonyManager.CALL_STATE_RINGING:
+ if(incomingNumber==null)
+ return;
+ final String name=Utils.callerId(context, incomingNumber);
+ if(name==null)
+ Utils.sendMessage(context, toNonNull(MessageType.PHONE_STATE),
+ context.getString(phone_status_ringing)+incomingNumber);
+ else
+ Utils.sendMessage(context, toNonNull(MessageType.PHONE_STATE),
+ context.getString(phone_status_ringing)+incomingNumber+" ("+name+")");
+ break;
+ case TelephonyManager.CALL_STATE_OFFHOOK:
+ Utils.sendMessage(context, toNonNull(MessageType.PHONE_STATE),
+ toNonNull(context.getString(phone_status_offhook)));
+ break;
+ }
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import java.io.IOException;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceActivity;
+import android.util.Log;
+import android.widget.Toast;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Preference activity. Lets the user change app preferences.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class FonBotPreferenceActivity extends PreferenceActivity {
+ /**
+ * Preference for toggling device admin permissions.
+ */
+ private CheckBoxPreference adminPreference;
+
+ @SuppressWarnings("deprecation")
+ @Override
+ protected void onCreate(@Nullable final Bundle icicle) {
+ super.onCreate(icicle);
+ setTitle("FonBot Preferences");
+ addPreferencesFromResource(R.xml.prefs);
+
+ adminPreference=(CheckBoxPreference) findPreference("admin");
+ final DevicePolicyManager dpm=(DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
+ final ComponentName adminReceiver=new ComponentName(this,FonBotAdminReceiver.class);
+ adminPreference.setChecked(dpm.isAdminActive(adminReceiver));
+
+ adminPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+
+ @Override
+ public boolean onPreferenceChange(final @Nullable Preference preference, final @Nullable Object newValue) {
+ if(newValue==null){
+ Log.wtf(FonBotPreferenceActivity.class.getName(), "newValue in OnPreferenceChange is null");
+ throw new AssertionError("Log.wtf did not terminate the process");
+ }
+
+ final boolean isChecked=((Boolean) newValue).booleanValue();
+ if(isChecked){
+ if(dpm.isAdminActive(adminReceiver))
+ return true;
+
+ final Intent intent=new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
+ intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, adminReceiver);
+ startActivityForResult(intent, 0);
+ return false;
+ }
+ dpm.removeActiveAdmin(adminReceiver);
+ return true;
+ }
+ });
+
+ final CheckBoxPreference foregroundPreference=(CheckBoxPreference) findPreference("foreground");
+ foregroundPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(final @Nullable Preference preference, final @Nullable Object newValue) {
+ startService(new Intent(FonBotPreferenceActivity.this, FonBotMainService.class));
+ return true;
+ }
+ });
+
+ final CheckBoxPreference systemPreference=(CheckBoxPreference) findPreference("system");
+ final ApplicationInfo info=getApplicationInfo();
+ final boolean isSystem=(info.flags&ApplicationInfo.FLAG_SYSTEM)!=0;
+ systemPreference.setChecked(isSystem);
+ systemPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(final @Nullable Preference preference, final @Nullable Object newValue) {
+ if(newValue==null)
+ return false;
+ final boolean isChecked=((Boolean)newValue).booleanValue();
+ if(isChecked==isSystem)
+ return true;
+
+ try {
+ final String remountCommand="mount -o remount,rw /system";
+ final String copyToSystemCommand="cp "+info.sourceDir+" /system/app/FonBot.apk";
+ final String chmodSystemCommand="chmod 644 /system/app/FonBot.apk";
+ final String copyToUserCommand="cp "+info.sourceDir+" /data/app/FonBot.apk";
+ final String chmodUserCommand="chmod 644 /data/app/FonBot.apk";
+ final String rmCommand="rm "+info.sourceDir;
+
+ if(isChecked){
+ Runtime.getRuntime().exec(new String[]{
+ "su", "-c",
+ remountCommand+';'+copyToSystemCommand+';'+chmodSystemCommand+';'+rmCommand
+ }).waitFor();
+
+ Toast.makeText(FonBotPreferenceActivity.this,
+ "Reboot to make FonBot a system application", Toast.LENGTH_LONG).show();
+ } else {
+ Runtime.getRuntime().exec(new String[]{
+ "su", "-c",
+ remountCommand+';'+copyToUserCommand+';'+chmodUserCommand+';'+rmCommand
+ }).waitFor();
+
+ Toast.makeText(FonBotPreferenceActivity.this,
+ "Reboot to make FonBot a non-system application", Toast.LENGTH_LONG).show();
+ }
+ } catch (IOException e) {
+ return false;
+ } catch (InterruptedException e) {
+ return false;
+ }
+ return true;
+ }
+ });
+ }
+
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
+ final DevicePolicyManager dpm=(DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
+ final ComponentName adminReceiver=new ComponentName(this,FonBotAdminReceiver.class);
+ adminPreference.setChecked(dpm.isAdminActive(adminReceiver));
+ }
+
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.google.android.gcm.GCMBaseIntentService;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Service that responds to GCM events
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public class GCMIntentService extends GCMBaseIntentService {
+ /** Handler instance */
+ private final Handler handler=new Handler(Looper.getMainLooper());
+
+ /**
+ * Constructs a GCMIntentService with the iEval sender id
+ */
+ public GCMIntentService() {
+ super(FonBotApplication.GCM_SENDER_ID);
+ }
+
+ @Override
+ protected void onError(@Nullable final Context context, @Nullable final String errID) {
+ //TODO error handling here
+ }
+
+ @Override
+ protected void onMessage(@Nullable final Context context, @Nullable final Intent intent) {
+ handler.post(new Runnable(){
+ @Override
+ public void run() {
+ new PollServerAsyncTask().execute();
+ }
+ });
+ }
+
+ @Override
+ protected void onRegistered(@Nullable final Context context, @Nullable final String regID) {
+ FonBotApplication.instance.regID=regID;
+ }
+
+ @Override
+ protected void onUnregistered(@Nullable final Context context, @Nullable final String regID) {
+ //do nothing
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * POJO that represents a HTTP header
+ *
+ * @author Marius Gavrilescu
+ */
+final class Header{
+ /**
+ * The header name
+ */
+ public final String name;
+ /**
+ * The header value
+ */
+ public final String value;
+
+ /**
+ * Constructs a Header from its two parts.
+ *
+ * @param name the name of the header
+ * @param value the value of the header
+ */
+ public Header(final String name, final String value){
+ this.name=name;
+ this.value=value;
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.Date;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.Utils.Command;
+import ro.ieval.fonbot.Utils.MessageType;
+import ro.ieval.fonbot.Utils.OngoingEvent;
+import ro.ieval.fonbot.Utils.RingerMode;
+import ro.ieval.fonbot.Utils.WipeType;
+import android.annotation.SuppressLint;
+import android.app.AlarmManager;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.graphics.ImageFormat;
+import android.hardware.Camera;
+import android.hardware.Camera.PictureCallback;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.Vibrator;
+import android.preference.PreferenceManager;
+import android.provider.BaseColumns;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.Settings.Secure;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeech.OnInitListener;
+import android.support.v4.app.NotificationCompat;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.widget.Toast;
+
+import com.android.internal.telephony.ITelephony;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Implementation of all FonBot commands. The methods of this class do not do argument checking.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+final class Heavy {
+ /**
+ * LocationListener that sends notifications to the user.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ private static final class FonBotLocationListener implements LocationListener {
+ /** Context instance */
+ private final Context context;
+ /** Destination address for notifications */
+ private final Address replyTo;
+
+ /**
+ * Construct a FonBotLocationListener.
+ *
+ * @param context Context instance
+ * @param replyTo the reply address
+ */
+ FonBotLocationListener(final Context context, final Address replyTo) {
+ this.context=context;
+ this.replyTo=replyTo;
+ }
+
+ @Override
+ public void onLocationChanged(@Nullable final Location loc) {
+ if(loc==null)
+ return;
+ final StringBuilder sb=new StringBuilder(toNonNull(context.getString(location)));
+ sb.append(": ");
+ sb.append(toNonNull(context.getString(latitude)));
+ sb.append(": ");
+ sb.append(loc.getLatitude());
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(longitude)));
+ sb.append(": ");
+ sb.append(loc.getLongitude());
+
+ if(loc.hasAccuracy()){
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(accuracy)));
+ sb.append(": ");
+ sb.append(loc.getAccuracy());
+ }
+
+ if(loc.hasAltitude()){
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(altitude)));
+ sb.append(": ");
+ sb.append(loc.getAltitude());
+ }
+
+ if(loc.hasBearing()){
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(bearing)));
+ sb.append(": ");
+ sb.append(loc.getBearing());
+ }
+
+ if(loc.hasSpeed()){
+ sb.append(", ");
+ sb.append(toNonNull(context.getString(speed)));
+ sb.append(": ");
+ sb.append(loc.getSpeed());
+ }
+
+ final Date locationDate=new Date(loc.getTime());
+ sb.append(" ");
+ sb.append(toNonNull(context.getString(at)));
+ sb.append(locationDate.toString());
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), toNonNull(sb.toString()));
+ }
+
+ @Override
+ public void onProviderDisabled(@Nullable final String provider) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), location_provider_disabled, provider);
+ }
+
+ @Override
+ public void onProviderEnabled(@Nullable final String provider) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), location_provider_enabled, provider);
+ }
+
+ @Override
+ public void onStatusChanged(@Nullable final String provider, final int status, @Nullable final Bundle extras) {
+ final int state;
+ switch(status){
+ case LocationProvider.AVAILABLE:
+ state=location_provider_available;
+ break;
+ case LocationProvider.TEMPORARILY_UNAVAILABLE:
+ state=location_provider_temporary_unavailable;
+ break;
+ case LocationProvider.OUT_OF_SERVICE:
+ state=location_provider_out_of_service;
+ break;
+ default:
+ state=location_provider_unknown_state;
+ }
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), state, provider);
+ }
+ }
+ /**
+ * Currently active FonBotLocationListener
+ */
+ private static FonBotLocationListener locationListener = null;
+
+ /**
+ * AsyncTask that sends a byte[] to a server
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ *
+ */
+ private static class SendDataAsyncTask extends AsyncTask<Void, Void, Void>{
+ /**
+ * Context instance used by this class
+ */
+ private final Context context;
+ /**
+ * Server hostname
+ */
+ private final String hostname;
+ /**
+ * Server port
+ */
+ private final int port;
+ /**
+ * Data to send
+ */
+ private final byte[] data;
+ /**
+ * Address for sending back errors and success messages
+ */
+ private final Address replyTo;
+
+ /**
+ * Constructs a SendDataAsyncTasks from its parameters
+ *
+ * @param context the context
+ * @param replyTo the reply Address
+ * @param hostname the server hostname
+ * @param port the server port
+ * @param data the data to send
+ */
+ public SendDataAsyncTask(final Context context,final Address replyTo, final String hostname, final int port, final byte[] data) {//NOPMD array is immutable
+ super();
+ this.context=context;
+ this.hostname=hostname;
+ this.port=port;
+ this.data=data;
+ this.replyTo=replyTo;
+ }
+
+ @Override
+ protected @Nullable Void doInBackground(@Nullable final Void... params) {
+ final Socket sock;
+ try {
+ sock = new Socket(hostname, port);
+ try {
+ sock.getOutputStream().write(data);
+ } catch (IOException e) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), error_writing_to_socket, e.getMessage());
+ return null;
+ }
+
+ try {
+ sock.close();
+ } catch (IOException e) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), cannot_close_socket, e.getMessage());
+ return null;
+ }
+ } catch (UnknownHostException e) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), unknown_host, hostname);
+ return null;
+ } catch (IOException e) {
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), cannot_connect_to_host_on_port, hostname, Integer.valueOf(port));
+ return null;
+ }
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), photo_sent);
+ return null;
+ }
+ }
+
+ /**
+ * PictureCallback that sends the picture to a server.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ private static final class FonBotPictureCallback implements PictureCallback{
+ /** Server hostname */
+ private final String hostname;
+ /** Server port */
+ private final int port;
+ /** Context instance */
+ private final Context context;
+ /** Reply address */
+ private final Address replyTo;
+
+ /**
+ * Construct a FonBotPictureCallback.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param hostname server hostname
+ * @param port server port
+ */
+ FonBotPictureCallback(final Context context, final Address replyTo, final String hostname, final int port) {
+ this.hostname=hostname;
+ this.port=port;
+ this.context=context;
+ this.replyTo=replyTo;
+ }
+
+ @Override
+ @SuppressWarnings("hiding")
+ public void onPictureTaken(final @Nullable byte[] data, final @Nullable Camera camera) {
+ if(camera==null || data==null)
+ return;
+ camera.stopPreview();
+ stopCamera();
+ Utils.sendMessage(toNonNull(context), toNonNull(replyTo), sending_photo);
+ new SendDataAsyncTask(toNonNull(context), toNonNull(replyTo), toNonNull(hostname), port, data).execute();
+ }
+ }
+
+ /**
+ * Get help for a particular command
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param command command to get help for
+ */
+ public static void help(final Context context, final Address replyTo, final Command command){//NOPMD method is a big switch statement. Nothing confusing.
+ switch(command){
+ case ANSWER:
+ Utils.sendMessage(context, replyTo, answer_help);
+ break;
+ case BATT:
+ Utils.sendMessage(context, replyTo, batt_help);
+ break;
+ case BLUETOOTH:
+ Utils.sendMessage(context, replyTo, bluetooth_help);
+ break;
+ case CALLLOG:
+ Utils.sendMessage(context, replyTo, calllog_help);
+ break;
+ case CONTACTS:
+ Utils.sendMessage(context, replyTo, contacts_help);
+ break;
+ case DATA:
+ Utils.sendMessage(context, replyTo, data_help);
+ break;
+ case DELNOTIFICATION:
+ Utils.sendMessage(context, replyTo, delnotification_help, Utils.join(", ", toNonNull(MessageType.values())));
+ break;
+ case DIAL:
+ Utils.sendMessage(context, replyTo, dial_help);
+ break;
+ case DIALOG:
+ Utils.sendMessage(context, replyTo, dialog_help);
+ break;
+ case DISABLE:
+ Utils.sendMessage(context, replyTo, disable_help, Utils.join(", ", toNonNull(Command.values())));
+ break;
+ case ECHO:
+ Utils.sendMessage(context, replyTo, echo_help);
+ break;
+ case ENABLE:
+ Utils.sendMessage(context, replyTo, enable_help, Utils.join(", ", toNonNull(Command.values())));
+ break;
+ case FLASH:
+ Utils.sendMessage(context, replyTo, flash_help);
+ break;
+ case GLOCATION:
+ Utils.sendMessage(context, replyTo, glocation_help);
+ break;
+ case GPS:
+ Utils.sendMessage(context, replyTo, gps_help);
+ break;
+ case HANGUP:
+ Utils.sendMessage(context, replyTo, hangup_help);
+ break;
+ case HELP:
+ Utils.sendMessage(context, replyTo, help_help, Utils.join(", ",toNonNull(Command.values())));
+ break;
+ case LAUNCH:
+ Utils.sendMessage(context, replyTo, launch_help);
+ break;
+ case LOCATION:
+ Utils.sendMessage(context, replyTo, location_help, Utils.join(", ",toNonNull(Utils.LocationProvider.values())));
+ break;
+ case LOCK:
+ Utils.sendMessage(context, replyTo, lock_help);
+ break;
+ case LS:
+ Utils.sendMessage(context, replyTo, ls_help);
+ break;
+ case NCFILE:
+ Utils.sendMessage(context, replyTo, ncfile_help);
+ break;
+ case NEXT:
+ Utils.sendMessage(context, replyTo, next_help);
+ break;
+ case NOLOCATION:
+ Utils.sendMessage(context, replyTo, nolocation_help);
+ break;
+ case PAUSE:
+ Utils.sendMessage(context, replyTo, pause_help);
+ break;
+ case PHOTO:
+ Utils.sendMessage(context, replyTo, photo_help);
+ break;
+ case PLAY:
+ Utils.sendMessage(context, replyTo, play_help);
+ break;
+ case POLL:
+ Utils.sendMessage(context, replyTo, poll_help);
+ break;
+ case PREV:
+ Utils.sendMessage(context, replyTo, prev_help);
+ break;
+ case RING:
+ Utils.sendMessage(context, replyTo, ring_help);
+ break;
+ case RINGER:
+ Utils.sendMessage(context, replyTo, ringer_help, Utils.join(", ", toNonNull(RingerMode.values())));
+ break;
+ case RM:
+ Utils.sendMessage(context, replyTo, rm_help);
+ break;
+ case SETNOTIFICATION:
+ Utils.sendMessage(context, replyTo, setnotification_help, Utils.join(", ", toNonNull(MessageType.values())));
+ break;
+ case SETPASSWORD:
+ Utils.sendMessage(context, replyTo, setpassword_help);
+ break;
+ case SMS:
+ Utils.sendMessage(context, replyTo, sms_help);
+ break;
+ case SMSLOG:
+ Utils.sendMessage(context, replyTo, smslog_help);
+ break;
+ case SPEAK:
+ Utils.sendMessage(context, replyTo, speak_help);
+ break;
+ case TOAST:
+ Utils.sendMessage(context, replyTo, toast_help);
+ break;
+ case VIBRATE:
+ Utils.sendMessage(context, replyTo, vibrate_help);
+ break;
+ case VIEW:
+ Utils.sendMessage(context, replyTo, view_help);
+ break;
+ case WIFI:
+ Utils.sendMessage(context, replyTo, wifi_help);
+ break;
+ case WIPE:
+ Utils.sendMessage(context, replyTo, wipe_help, Utils.WIPE_CONFIRM_STRING);
+ break;
+ case REBOOT:
+ Utils.sendMessage(context, replyTo, reboot_help);
+ break;
+ case SHUTDOWN:
+ Utils.sendMessage(context, replyTo, shutdown_help);
+ break;
+ case NOTIFY:
+ Utils.sendMessage(context, replyTo, notify_help);
+ }
+ }
+
+ /**
+ * Camera instance.
+ *
+ * @see #startCamera(Context, Address)
+ * @see #stopCamera()
+ */
+ private static Camera camera;
+ /**
+ * Ringtone used by the {@link Utils.Command#RING RING} command.
+ *
+ * @see #setupRingtone(Context)
+ */
+ private static Ringtone ringtone;
+ /**
+ * Saved ringer volume.
+ *
+ * @see #startAlarm(Context, Address)
+ * @see #stopAlarm(Context, Address)
+ */
+ private static int savedRingVolume;
+ /**
+ * Saved ringer mode.
+ *
+ * @see #startAlarm(Context, Address)
+ * @see #stopAlarm(Context, Address)
+ */
+ private static int savedRingerMode;
+
+ /** Private constructor */
+ private Heavy(){
+ //do nothing
+ }
+
+ /**
+ * Convert a phone number type to a string
+ *
+ * @param context Context instance
+ * @param type phone number type
+ * @param label name of a custom phone type
+ * @return the phone number type
+ */
+ private static @Nullable String phoneNumberType(final Context context, final int type, final @Nullable String label) {
+ switch(type){
+ case BaseTypes.TYPE_CUSTOM:
+ return label;
+ case Phone.TYPE_ASSISTANT:
+ return context.getString(phone_numer_type_assistant);
+ case Phone.TYPE_CALLBACK:
+ return context.getString(phone_number_type_callback);
+ case Phone.TYPE_CAR:
+ return context.getString(phone_number_type_car);
+ case Phone.TYPE_COMPANY_MAIN:
+ return context.getString(phone_number_type_company_main);
+ case Phone.TYPE_FAX_HOME:
+ return context.getString(phone_number_type_home_fax);
+ case Phone.TYPE_FAX_WORK:
+ return context.getString(phone_number_type_work_fax);
+ case Phone.TYPE_HOME:
+ return context.getString(phone_number_type_home);
+ case Phone.TYPE_ISDN:
+ return context.getString(phone_number_type_isdn);
+ case Phone.TYPE_MAIN:
+ return context.getString(phone_number_type_main);
+ case Phone.TYPE_MMS:
+ return context.getString(phone_number_type_mms);
+ case Phone.TYPE_MOBILE:
+ return context.getString(phone_number_type_mobile);
+ case Phone.TYPE_OTHER:
+ return context.getString(phone_number_type_other);
+ case Phone.TYPE_OTHER_FAX:
+ return context.getString(phone_number_type_other_fax);
+ case Phone.TYPE_PAGER:
+ return context.getString(phone_number_type_pager);
+ case Phone.TYPE_RADIO:
+ return context.getString(phone_number_type_radio);
+ case Phone.TYPE_TELEX:
+ return context.getString(phone_number_type_telex);
+ case Phone.TYPE_TTY_TDD:
+ return context.getString(phone_number_type_textphone);
+ case Phone.TYPE_WORK:
+ return context.getString(phone_number_type_work);
+ case Phone.TYPE_WORK_MOBILE:
+ return context.getString(phone_number_type_work_mobile);
+ case Phone.TYPE_WORK_PAGER:
+ return context.getString(phone_number_type_work_pager);
+ }
+
+ return context.getString(phone_number_type_unknown, Integer.valueOf(type));
+ }
+
+ /**
+ * Setup the ringtone used by the {@link Utils.Command#RING RING} command
+ *
+ * @param context Context
+ */
+ private static void setupRingtone(final Context context){
+ if(ringtone==null){//NOPMD not supposed to be thread-safe
+ final Uri alert=RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+ ringtone=RingtoneManager.getRingtone(context, alert);
+ }
+ }
+
+ /**
+ * Make the phone start ringing. Turns up the volume and sets the ringer mode to NORMAL
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ private static void startAlarm(final Context context, final Address replyTo){
+ Utils.registerOngoing(context, toNonNull(OngoingEvent.RING));
+ final AudioManager man=(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ savedRingerMode=man.getRingerMode();
+ man.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ savedRingVolume=man.getStreamVolume(AudioManager.STREAM_RING);
+ man.setStreamVolume(AudioManager.STREAM_RING, man.getStreamMaxVolume(AudioManager.STREAM_RING), 0);
+ Utils.sendMessage(context, replyTo, ringing);
+ ringtone.play();
+ }
+
+ /**
+ * Get a camera instance.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ private static void startCamera(final Context context, final Address replyTo){
+ if(camera!=null)
+ return;
+ try{
+ camera=Camera.open();
+ } catch (Exception e){
+ Utils.sendMessage(context, replyTo, cannot_grab_camera);
+ }
+ }
+
+ /**
+ * Make the phone stop ringing. Restores the volume and ringer mode.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ private static void stopAlarm(final Context context, final Address replyTo){
+ Utils.unregisterOngoing(context, toNonNull(OngoingEvent.RING));
+ final AudioManager man=(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ Utils.sendMessage(context, replyTo, no_longer_ringing);
+ ringtone.stop();
+ man.setStreamVolume(AudioManager.STREAM_RING, savedRingVolume, 0);
+ man.setRingerMode(savedRingerMode);
+ }
+
+ /**
+ * Release the previously grabbed camera instance
+ *
+ * @see #startCamera(Context, Address)
+ */
+ private static void stopCamera(){
+ if(camera==null)
+ return;
+ camera.release();
+ camera=null;
+ }
+
+ /**
+ * Send battery status information to an Address
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ *
+ * @see #describeBatteryLevel(Context, Address, MessageType)
+ */
+ public static void batt(final Context context, final Address replyTo){
+ describeBatteryLevel(context, replyTo, null);
+ }
+
+ /**
+ * Show the bluetooth radio status.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ */
+ public static void bluetooth(final Context context, final Address replyTo) {
+ final BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
+ if(adapter==null){
+ Utils.sendMessage(context, replyTo, no_bluetooth_adapter);
+ return;
+ }
+
+ if(adapter.isEnabled())
+ Utils.sendMessage(context, replyTo, bluetooth_on);
+ else
+ Utils.sendMessage(context, replyTo, bluetooth_off);
+ }
+
+ /**
+ * Set the bluetooth radio status.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param on the requested radio status
+ */
+ public static void bluetooth(final Context context, final Address replyTo, final boolean on){
+ final BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
+ if(adapter==null){
+ Utils.sendMessage(context, replyTo, no_bluetooth_adapter);
+ return;
+ }
+
+ if(on) {
+ adapter.enable();
+ Utils.sendMessage(context, replyTo, enabling_bluetooth);
+ }
+ else {
+ adapter.disable();
+ Utils.sendMessage(context, replyTo, disabling_bluetooth);
+ }
+ }
+
+ /**
+ * Cancel an ongoing event.
+ *
+ * @param context Context instance
+ * @param event the event to cancel
+ */
+ public static void cancelOngoing(final Context context, final OngoingEvent event){
+ switch(event){
+ case LOCATION:
+ nolocation(context, toNonNull(Address.BLACKHOLE));
+ break;
+ case POLL:
+ poll(context, toNonNull(Address.BLACKHOLE), 0);
+ break;
+ case RING:
+ ring(context, toNonNull(Address.BLACKHOLE), false);
+ break;
+ }
+ }
+
+ /**
+ * Send the last calls to an Address.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param numCalls how many calls to send
+ */
+ public static void calllog(final Context context, final Address replyTo, final int numCalls) {
+ final String[] fields = {
+ Calls.TYPE, Calls.NUMBER, Calls.CACHED_NAME, Calls.DURATION, Calls.DATE
+ };
+
+ final Cursor cursor = context.getContentResolver().query(
+ Calls.CONTENT_URI,
+ fields,
+ null,
+ null,
+ Calls.DATE + " DESC"
+ );
+
+ if (cursor.moveToFirst()) {
+ do {
+ final StringBuilder sb=new StringBuilder(50);//NOPMD different strings
+ final int type=cursor.getInt(0);
+ final String from=cursor.getString(1);
+
+ switch(type){
+ case Calls.INCOMING_TYPE:
+ sb.append(context.getString(incoming_call_from, from));
+ break;
+ case Calls.MISSED_TYPE:
+ sb.append(context.getString(missed_call_from, from));
+ break;
+ case Calls.OUTGOING_TYPE:
+ sb.append(context.getString(outgoing_call_to, from));
+ break;
+ }
+
+ if (cursor.getString(2) != null)
+ sb.append('(').append(cursor.getString(2)).append(") ");
+
+ sb.append(context.getString(duration_seconds_starting_at,
+ Long.valueOf(cursor.getLong(3)),
+ new Date(cursor.getLong(4))));
+
+ Utils.sendMessage(context, replyTo, toNonNull(sb.toString()));
+ } while (cursor.moveToNext() && cursor.getPosition() < numCalls);
+ }
+
+ cursor.close();
+ }
+
+ /**
+ * Search for contacts by name/nickname and send matching entries to an Address.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param name name/nickname part to search for
+ */
+ @SuppressLint("StringFormatMatches")
+ public static void contacts(final Context context, final Address replyTo, final String name){
+ final Cursor cursor=context.getContentResolver().query(Uri.withAppendedPath(
+ Contacts.CONTENT_FILTER_URI, name),
+ new String[]{Contacts.DISPLAY_NAME, BaseColumns._ID, Contacts.LOOKUP_KEY},
+ null, null, Contacts.DISPLAY_NAME);
+
+ if(cursor.getCount()==0)
+ Utils.sendMessage(context, replyTo, no_matching_contacts_found);
+
+ while(cursor.moveToNext()){
+ final String[] fields = {
+ CommonDataKinds.Phone.NUMBER,
+ CommonDataKinds.Phone.TYPE,
+ CommonDataKinds.Phone.LABEL,
+ };
+
+ final Cursor inCursor=context.getContentResolver().query(Data.CONTENT_URI,
+ fields,
+ Data.CONTACT_ID+" = ? AND "+Data.MIMETYPE+ " = ?",
+ new String[]{Long.toString(cursor.getLong(1)), CommonDataKinds.Phone.CONTENT_ITEM_TYPE},
+ CommonDataKinds.Phone.LABEL);
+
+ while(inCursor.moveToNext())
+ Utils.sendMessage(context, replyTo, toNonNull(context.getString(contact_info,
+ cursor.getString(0),
+ inCursor.getString(0),
+ phoneNumberType(context, inCursor.getInt(1), inCursor.getString(2)))));
+
+ inCursor.close();
+ }
+
+ cursor.close();
+ }
+
+ /**
+ * Send battery status information to an Address or as a notification
+ *
+ * @param context Context instance
+ * @param replyTo Address to send the information to, if sending to a direct address. Null otherwise.
+ * @param type Notification type, if sending as a notification. Null otherwise.
+ */
+ public static void describeBatteryLevel(final Context context, final @Nullable Address replyTo, final @Nullable MessageType type) {
+ if(replyTo==null&&type==null)
+ return;
+ final Intent intent=context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ if(intent==null)
+ return;
+ final double level=intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+ final int scale=intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
+ final int plugged=intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ final int status=intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
+ final int temp=intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
+ final int volt=intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
+
+ final StringBuilder sb=new StringBuilder(100);
+ sb.append(context.getString(battery_level, Double.valueOf(level*100/scale)));
+
+ switch(plugged){
+ case 0:
+ sb.append(context.getString(not_plugged_in));
+ break;
+ case BatteryManager.BATTERY_PLUGGED_AC:
+ sb.append(context.getString(plugged_in_ac));
+ break;
+ case BatteryManager.BATTERY_PLUGGED_USB:
+ sb.append(context.getString(plugged_in_usb));
+ break;
+ case BatteryManager.BATTERY_PLUGGED_WIRELESS:
+ sb.append(context.getString(plugged_in_wireless));
+ break;
+ }
+
+ switch(status){
+ case BatteryManager.BATTERY_STATUS_CHARGING:
+ sb.append(context.getString(status_charging));
+ break;
+ case BatteryManager.BATTERY_STATUS_DISCHARGING:
+ sb.append(context.getString(status_discharging));
+ break;
+ case BatteryManager.BATTERY_STATUS_FULL:
+ sb.append(context.getString(status_full));
+ break;
+ case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
+ sb.append(context.getString(status_not_charging));
+ break;
+ case BatteryManager.BATTERY_STATUS_UNKNOWN:
+ sb.append(context.getString(status_unknown));
+ break;
+ }
+
+ sb.append(context.getString(temperature, Integer.valueOf(temp)));
+
+ sb.append(context.getString(voltage, Integer.valueOf(volt)));
+ if(type==null)
+ Utils.sendMessage(context, toNonNull(replyTo), toNonNull(sb.toString()));
+ else
+ Utils.sendMessage(context, type, toNonNull(sb.toString()));
+ }
+
+ /**
+ * Dial a phone number.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param nr phone number to dial
+ */
+ public static void dial(final Context context, final Address replyTo, final String nr){
+ final Intent intent=new Intent(Intent.ACTION_CALL,Uri.parse("tel:"+nr));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final String name=Utils.callerId(context, nr);
+ if(name==null)
+ Utils.sendMessage(context, replyTo, dialing, nr);
+ else
+ Utils.sendMessage(context, replyTo, dialing, nr+" ("+name+")");
+ context.startActivity(intent);
+ }
+
+ /**
+ * Show a dialog with a message and a list of buttons.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param message dialog message
+ * @param buttons dialog buttons
+ */
+ public static void dialog(final Context context, final Address replyTo, final String message, final String[] buttons){
+ final Intent intent=new Intent(context, DialogActivity.class);
+ intent.putExtra(DialogActivity.EXTRA_MESSAGE, message);
+ intent.putExtra(DialogActivity.EXTRA_BUTTONS, buttons);
+ intent.putExtra(DialogActivity.EXTRA_REPLYTO, replyTo.toString());
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|
+ Intent.FLAG_ACTIVITY_NEW_TASK|
+ Intent.FLAG_ACTIVITY_NO_USER_ACTION|
+ Intent.FLAG_FROM_BACKGROUND);
+ Utils.sendMessage(context, toNonNull(replyTo), showing_dialog);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Turns the flashlight on or off.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param on requested flashlight state
+ */
+ public static void flash(final Context context, final Address replyTo, final boolean on){
+ startCamera(context, replyTo);
+ if(camera==null)
+ return;
+ final Camera.Parameters parms=camera.getParameters();
+ if(on){
+ parms.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
+ camera.setParameters(parms);
+ } else {
+ parms.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
+ camera.setParameters(parms);
+ stopCamera();
+ }
+ }
+
+ /**
+ * Start sending location updates to an Address.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param provider LocationProvider
+ * @param minTime minimum time between two consecutive updates (in ms)
+ * @param minDistance minimum distance between two consecutive updates (in meters)
+ *
+ * @see LocationManager#requestLocationUpdates(String, long, float, LocationListener)
+ */
+ public static void location(final Context context, final Address replyTo, final String provider,final long minTime,final float minDistance){
+ final LocationManager man=(LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ if(locationListener!=null)
+ nolocation(context, toNonNull(Address.BLACKHOLE));
+ Utils.registerOngoing(context, toNonNull(OngoingEvent.LOCATION));
+ locationListener=new FonBotLocationListener(context, replyTo);
+ man.removeUpdates(locationListener);
+ final Location lastKnownLocation=man.getLastKnownLocation(provider);
+ if(lastKnownLocation!=null){
+ Utils.sendMessage(context, replyTo, last_known_location);
+ locationListener.onLocationChanged(lastKnownLocation);
+ }
+ Utils.sendMessage(context, replyTo, listening_for_location_updates);
+ man.requestLocationUpdates(provider, minTime, minDistance, locationListener);
+ }
+
+ /**
+ * Lock the phone.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void lock(final Context context, final Address replyTo) {
+ final DevicePolicyManager dpm=(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ dpm.lockNow();
+ Utils.sendMessage(context, replyTo, device_locked);
+ }
+
+ /**
+ * Send a command to a running instance of the music player
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param command command to send
+ */
+ public static void musicPlayerCommand(final Context context, final Address replyTo, final String command) {
+ final Intent intent=new Intent("com.android.music.musicservicecommand");
+ intent.putExtra("command", command);
+ context.sendBroadcast(intent);
+ Utils.sendMessage(context, replyTo, command_sent);
+ }
+
+ /**
+ * Send a file to a server.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param filename file to send
+ * @param hostname server hostname
+ * @param port server port
+ */
+ public static void ncfile(final Context context, final Address replyTo, final String filename,final String hostname,final int port){
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected @Nullable Void doInBackground(@Nullable final Void... params) {
+ final FileChannel in;
+ try{
+ in=new FileInputStream(filename).getChannel();
+ } catch (final FileNotFoundException e){
+ Utils.sendMessage(context, replyTo, file_not_found, filename);
+ return null;
+ }
+ final SocketChannel sock;
+ try{
+ sock = SocketChannel.open(new InetSocketAddress(hostname, port));
+ } catch (final IOException e){
+ Utils.sendMessage(context, replyTo, toNonNull(context.getString(
+ cannot_connect_to_host_on_port, hostname, Integer.valueOf(port))));
+ try {
+ in.close();
+ } catch (IOException ex) {
+ //ignored
+ }
+ return null;
+ }
+
+ try{
+ in.transferTo(0, in.size(), sock);
+ } catch (final IOException e){
+ Utils.sendMessage(context, replyTo, toNonNull(context.getString(
+ io_error, e.getMessage())));
+ } finally {
+ try{
+ in.close();
+ } catch (IOException e){
+ //ignored
+ }
+ try{
+ sock.close();
+ } catch(IOException e){
+ //ignored
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(@Nullable final Void result) {
+ Utils.sendMessage(context, replyTo, file_sent);
+ }
+ }.execute();
+ }
+
+ /**
+ * Stop sending location updates.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void nolocation(final Context context, final Address replyTo){
+ Utils.unregisterOngoing(context, toNonNull(OngoingEvent.LOCATION));
+ final LocationManager man=(LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ man.removeUpdates(locationListener);
+ locationListener=null;
+ Utils.sendMessage(context, replyTo, no_longer_listening_for_location_updates);
+ }
+
+ /**
+ * Take a photo and send it to a server.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param hostname server hostname
+ * @param port server port
+ */
+ public static void photo(final Context context, final Address replyTo, final String hostname, final int port){
+ startCamera(context, replyTo);
+ if(camera==null)
+ return;
+ final Camera.Parameters parms=camera.getParameters();
+ parms.setJpegQuality(70);
+ parms.setPictureFormat(ImageFormat.JPEG);
+ camera.setParameters(parms);
+
+ final SurfaceView fakeView=new SurfaceView(context);
+ try {
+ camera.setPreviewDisplay(fakeView.getHolder());
+ } catch (IOException e) {
+ Utils.sendMessage(context, replyTo, error_setting_preview_display);
+ return;
+ }
+ camera.startPreview();
+ final Handler handler=new Handler();
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ //ignored
+ }
+
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ camera.takePicture(null, null, new FonBotPictureCallback(context, replyTo, hostname, port));
+ }
+ });
+ }
+ }).start();
+ }
+
+ /**
+ * Send a directory listing to an Address
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param directory directory to list
+ */
+ public static void ls(final Context context, final Address replyTo, final String directory) {
+ final File[] files=new File(directory).listFiles();
+ if(files==null){
+ Utils.sendMessage(context, replyTo, string_is_not_a_directory, directory);
+ return;
+ }
+
+ final StringBuilder sb=new StringBuilder(context.getString(files_in_directory,directory));
+ for(final File file : files){
+ sb.append(file.getName());
+ if(file.isDirectory())
+ sb.append('/');
+ sb.append(" ");
+ }
+
+ Utils.sendMessage(context, replyTo, toNonNull(sb.toString()));
+ }
+
+ /**
+ * Make the phone start ringing if it is not ringing or stop ringing if it is.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void ring(final Context context, final Address replyTo){
+ setupRingtone(context);
+ if(ringtone==null){
+ Utils.sendMessage(context, replyTo, no_ringtone_found);
+ return;
+ }
+ if(ringtone.isPlaying())
+ stopAlarm(context, replyTo);
+ else
+ startAlarm(context, replyTo);
+ }
+
+ /**
+ * Make the phone start/stop ringing.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param on true if the phone should start ringing, false otherwise
+ */
+ public static void ring(final Context context, final Address replyTo, final boolean on){
+ setupRingtone(context);
+ if(ringtone==null){
+ Utils.sendMessage(context, replyTo, no_ringtone_found);
+ return;
+ }
+ if(on&&!ringtone.isPlaying())
+ startAlarm(context, replyTo);
+ else if(ringtone.isPlaying()&&!on)
+ stopAlarm(context, replyTo);
+ }
+
+ /**
+ * Send the current ringer mode to an Address
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ */
+ public static void ringer(final Context context, final Address replyTo){
+ final AudioManager man=(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ switch(man.getRingerMode()){
+ case AudioManager.RINGER_MODE_NORMAL:
+ Utils.sendMessage(context, replyTo, ringer_mode_normal);
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ Utils.sendMessage(context, replyTo, ringer_mode_vibrate);
+ break;
+ case AudioManager.RINGER_MODE_SILENT:
+ Utils.sendMessage(context, replyTo, ringer_mode_silent);
+ break;
+ default:
+ Utils.sendMessage(context, replyTo, unknown_ringer_mode);
+ }
+ }
+
+ /**
+ * Set the ringer mode.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param ringerMode requested ringer mode
+ *
+ * @see Utils.RingerMode
+ */
+ public static void ringer(final Context context, final Address replyTo, final int ringerMode){
+ final AudioManager man=(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ man.setRingerMode(ringerMode);
+ ringer(context, replyTo);
+ }
+
+ /**
+ * Remove a file or empty directory.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param filename file/empty directory to delete
+ */
+ public static void rm(final Context context, final Address replyTo, final String filename){
+ if(new File(filename).delete())
+ Utils.sendMessage(context, replyTo, file_deleted);
+ else
+ Utils.sendMessage(context, replyTo, error_while_deleting_file);
+ }
+
+ /**
+ * Clear the keyguard password.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @throws SecurityException if FonBot does not have device administration permissions
+ */
+ public static void setPassword(final Context context, final Address replyTo) throws SecurityException{
+ final DevicePolicyManager dpm=(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ dpm.resetPassword("", 0);
+ Utils.sendMessage(context, replyTo, password_cleared);
+ }
+
+ /**
+ * Change the keyguard password.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param password new password
+ * @throws SecurityException if FonBot does not have device administration permissions
+ */
+ public static void setPassword(final Context context, final Address replyTo, final String password) throws SecurityException{
+ final DevicePolicyManager dpm=(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ dpm.resetPassword(password, 0);
+ Utils.sendMessage(context, replyTo, password_set);
+ }
+
+ /**
+ * Send a text message.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param destination destination phone number
+ * @param text text message contents
+ */
+ public static void sms(final Context context, final Address replyTo, final String destination, final String text){
+ final SmsManager manager=SmsManager.getDefault();
+ final ArrayList<String> messages=manager.divideMessage(text);
+ if(messages.size() > 1)
+ Utils.sendMessage(context, replyTo, message_was_split_into_parts, Integer.valueOf(messages.size()));
+
+ final ArrayList<PendingIntent> sents=new ArrayList<PendingIntent>(messages.size());
+ final ArrayList<PendingIntent> delivereds=new ArrayList<PendingIntent>(messages.size());
+
+ final String name=Utils.callerId(context, destination);
+ final String fullDestination;
+ if(name==null)
+ fullDestination=destination;
+ else
+ fullDestination=destination+" ("+name+")";
+
+ for(int i=0;i<messages.size();i++){
+ final Intent sent=new Intent(context,SmsStatusReceiver.class);
+ sent.putExtra(SmsStatusReceiver.EXTRA_DESTINATION, fullDestination);
+ sent.putExtra(SmsStatusReceiver.EXTRA_PART, i+1);
+ sent.putExtra(SmsStatusReceiver.EXTRA_TOTAL, messages.size());
+ sent.putExtra(SmsStatusReceiver.EXTRA_REPLY_TO, replyTo.toString());
+ sent.setAction(SmsStatusReceiver.SENT_ACTION+i);//actions must be unique
+ sents.add(PendingIntent.getBroadcast(context, 0, sent, PendingIntent.FLAG_UPDATE_CURRENT));
+
+ final Intent delivered=new Intent(context, SmsStatusReceiver.class);
+ delivered.putExtra(SmsStatusReceiver.EXTRA_DESTINATION, fullDestination);
+ delivered.putExtra(SmsStatusReceiver.EXTRA_PART, i+1);
+ delivered.putExtra(SmsStatusReceiver.EXTRA_TOTAL, messages.size());
+ delivered.putExtra(SmsStatusReceiver.EXTRA_REPLY_TO, replyTo.toString());
+ delivered.setAction(SmsStatusReceiver.DELIVERED_ACTION+i);//actions must be unique
+ delivereds.add(PendingIntent.getBroadcast(context, 0, delivered, PendingIntent.FLAG_UPDATE_CURRENT));
+ }
+
+ Log.d(Heavy.class.getName(), "Sending sms to "+destination);
+ manager.sendMultipartTextMessage(destination, null, messages, sents, delivereds);
+ }
+
+ /**
+ * Send the last SMSes to an Address.
+ *
+ * @param context Context instance
+ * @param replyTo destination Address
+ * @param numSms how many SMSes to send
+ */
+ public static void smslog(final Context context, final Address replyTo, final int numSms) {
+ final String[] fields = {"type","address", "body", "date"};
+
+ final Cursor cursor = context.getContentResolver().query (
+ Uri.parse("content://sms"),
+ fields,
+ null,
+ null,
+ "date DESC"
+ );
+
+ if (cursor.moveToFirst()) {
+ do {
+ final String fromNumber=cursor.getString(1);
+ final String from;
+ final String name=Utils.callerId(context, Utils.toNonNull(fromNumber));
+ if(name==null)
+ from=fromNumber;
+ else
+ from=fromNumber+" ("+name+')';
+ final String message=cursor.getString(2).replace("\n", "\n ");
+ final Date date=new Date(cursor.getLong(3));
+
+ if(cursor.getInt(0)==1)
+ Utils.sendMessage(context, replyTo, incoming_message, from, message, date);
+ else
+ Utils.sendMessage(context, replyTo, outgoing_message, from, message, date);
+ } while (cursor.moveToNext() && cursor.getPosition() < numSms);
+ }
+
+ cursor.close();
+ }
+
+ /** TTS instance, only used by {@link #speak(Context, Address, String)} */
+ private static TextToSpeech tts;
+
+ /**
+ * Speak a String using the text-to-speech engine.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param text text to speak
+ */
+ public static void speak(final Context context, final Address replyTo, final String text){
+ tts=new TextToSpeech(context, new OnInitListener() {
+ @Override
+ public void onInit(final int status) {
+ if(status==TextToSpeech.SUCCESS){
+ Utils.sendMessage(context, replyTo, speaking);
+ tts.speak(text, TextToSpeech.QUEUE_ADD, null);
+ } else
+ Utils.sendMessage(context, replyTo, tts_engine_not_available);
+ }
+ });
+ }
+
+ /**
+ * Show a toast notification with the default duration.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param text toast text
+ */
+ public static void toast(final Context context, final Address replyTo, final String text){
+ toast(context, replyTo, text, Toast.LENGTH_SHORT);
+ }
+
+ /**
+ * Show a toast notification.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param text toast text
+ * @param duration toast duration
+ */
+ public static void toast(final Context context, final Address replyTo, final String text, final int duration){
+ Toast.makeText(context,text,duration).show();
+ Utils.sendMessage(context, replyTo, toast_shown);
+ }
+
+ /**
+ * Make the phone vibrate for a number of milliseconds.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param ms vibrate duration, in milliseconds
+ */
+ public static void vibrate(final Context context, final Address replyTo, final long ms){
+ final Vibrator v=(Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ Utils.sendMessage(context, replyTo, vibrating);
+ v.vibrate(ms);
+ }
+
+ /**
+ * View an URI in an appropriate activity.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param uri URI to view
+ */
+ public static void view(final Context context, final Address replyTo, final Uri uri) {
+ try{
+ final Intent intent=new Intent(Intent.ACTION_VIEW);
+ intent.setData(uri);
+ intent.setFlags(Intent.FLAG_FROM_BACKGROUND|Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ Utils.sendMessage(context, replyTo, url_opened);
+ } catch(ActivityNotFoundException e){
+ Utils.sendMessage(context, replyTo, no_activity_found_for_this_url);
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, invalid_url);
+ }
+ }
+
+ /**
+ * Get the current WiFi state.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void wifi(final Context context, final Address replyTo){
+ final WifiManager man=(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ if(man.isWifiEnabled())
+ Utils.sendMessage(context, replyTo, wifi_on);
+ else
+ Utils.sendMessage(context, replyTo, wifi_off);
+ }
+
+ /**
+ * Set the WiFi state.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param on the requested WiFi state
+ */
+ public static void wifi(final Context context, final Address replyTo, final boolean on){
+ final WifiManager man=(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ man.setWifiEnabled(on);
+ if(on)
+ Utils.sendMessage(context, replyTo, enabling_wifi);
+ else
+ Utils.sendMessage(context, replyTo, disabling_wifi);
+ }
+
+ /**
+ * Factory reset the phone, optionally deleting the SD card too.
+ *
+ * @param context Context instance
+ * @param type {@link Utils.WipeType} instance
+ * @throws SecurityException if FonBot does not have device administration permissions
+ */
+ @SuppressLint("InlinedApi")
+ public static void wipe(final Context context, final WipeType type) throws SecurityException{
+ final DevicePolicyManager dpm=(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ switch(type){
+ case DATA:
+ dpm.wipeData(0);
+ break;
+ case FULL:
+ dpm.wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE);
+ break;
+ }
+ }
+
+ /**
+ * Disable a Command. The command cannot be used until enabled again with the {@link Utils.Command#ENABLE ENABLE} command.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param command Command to disable
+ */
+ public static void disable(final Context context, final Address replyTo, final Command command){
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putBoolean(command+"disabled", true).commit();
+ Utils.sendMessage(context, replyTo, command_disabled, command);
+ }
+
+ /**
+ * Re-enable a disabled Command.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param command Command to re-enable
+ */
+ public static void enable(final Context context, final Address replyTo, final Command command){
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .remove(command+"disabled").commit();
+ Utils.sendMessage(context, replyTo, command_enabled, command);
+
+ }
+
+ /**
+ * Check whether a Command is disabled.
+ *
+ * @param context Context instance
+ * @param command Command to check
+ * @return true if the Command is disabled, false otherwise
+ */
+ public static boolean isCommandDisabled(final Context context, final Command command){
+ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(command+"disabled", false);
+ }
+
+ /**
+ * Poll the server for pending commands.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void poll(final Context context, final Address replyTo) {
+ Utils.sendMessage(context, replyTo, polling_server);
+ new PollServerAsyncTask().execute();
+ }
+
+ /**
+ * Change the server poll interval.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param ms server poll interval in milliseconds. If 0, server poll is disabled
+ */
+ public static void poll(final Context context, final Address replyTo, final long ms){
+ final AlarmManager man=(AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ final Intent pollAlarm=new Intent(context, LocalBroadcastReceiver.class);
+ pollAlarm.setAction(LocalBroadcastReceiver.ACTION_POLL_ALARM);
+ final PendingIntent intent=PendingIntent.getBroadcast(context, 0, pollAlarm, 0);
+ if(ms==0){
+ Utils.unregisterOngoing(context, toNonNull(OngoingEvent.POLL));
+ man.cancel(intent);
+ Utils.sendMessage(context, replyTo, polling_stopped);
+ } else {
+ Utils.registerOngoing(context, toNonNull(OngoingEvent.POLL));
+ man.setRepeating(AlarmManager.RTC_WAKEUP, 0, ms, intent);
+ Utils.sendMessage(context, replyTo, polling_every_milliseconds, Long.valueOf(ms));
+ }
+ }
+
+ /**
+ * Get an instance of {@link ITelephony}
+ *
+ * @param context Context instance
+ * @return an instance of {@link ITelephony}
+ * @throws NoSuchMethodException thrown by reflection
+ * @throws IllegalArgumentException thrown by reflection
+ * @throws IllegalAccessException thrown by reflection
+ * @throws InvocationTargetException thrown by reflection
+ */
+ private static ITelephony getITelephony(final Context context) throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
+ final TelephonyManager man=(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ final Method m=TelephonyManager.class.getDeclaredMethod("getITelephony");
+ m.setAccessible(true);
+ return toNonNull((ITelephony) m.invoke(man));
+ }
+
+ /**
+ * Hang up the phone.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void hangup(final Context context, final Address replyTo){
+ try{
+ getITelephony(context).endCall();
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, exception_while_hanging_up_call,
+ e.getClass().getName(), e.getMessage());
+ }
+ }
+
+ /**
+ * Answer the phone if it is ringing.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void answer(final Context context, final Address replyTo){
+ try{
+ getITelephony(context).answerRingingCall();
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, exception_while_answering_call,
+ e.getClass().getName(), e.getMessage());
+ }
+ }
+
+ /**
+ * Launch a package.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param pkg name of the package to launch
+ */
+ public static void launch(final Context context, final Address replyTo, final String pkg){
+ final Intent intent=context.getPackageManager().getLaunchIntentForPackage(pkg);
+ if(intent==null){
+ Utils.sendMessage(context, replyTo, no_such_package);
+ return;
+ }
+ context.startActivity(intent);
+ Utils.sendMessage(context, replyTo, app_launched);
+ }
+
+ /**
+ * Get the mobile data enabled status.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void data(final Context context, final Address replyTo){
+ try{
+ final ConnectivityManager man=(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final Method m=ConnectivityManager.class.getDeclaredMethod("getMobileDataEnabled");
+ m.setAccessible(true);
+ if(((Boolean)m.invoke(man)).booleanValue())
+ Utils.sendMessage(context, replyTo, data_on);
+ else
+ Utils.sendMessage(context, replyTo, data_off);
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, exception_while_determining_data_state,
+ e.getClass().getName(), e.getMessage());
+ }
+ }
+
+ /**
+ * Set the mobile data enabled status.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param enable whether to enable mobile data
+ */
+ public static void data(final Context context, final Address replyTo, final boolean enable) {
+ try{
+ if(enable){
+ getITelephony(context).enableDataConnectivity();
+ Utils.sendMessage(context, replyTo, enabling_data);
+ } else {
+ getITelephony(context).disableDataConnectivity();
+ Utils.sendMessage(context, replyTo, disabling_data);
+ }
+ } catch(Exception e){
+ Utils.sendMessage(context, replyTo, exception_while_getting_itelephony,
+ e.getClass().getName(), e.getMessage());
+ }
+ }
+
+ /**
+ * Get the GPS status.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void gps(final Context context, final Address replyTo){
+ if(Secure.isLocationProviderEnabled(context.getContentResolver(), LocationManager.GPS_PROVIDER))
+ Utils.sendMessage(context, replyTo, gps_on);
+ else
+ Utils.sendMessage(context, replyTo, gps_off);
+ }
+
+ /**
+ * Set the GPS status.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param enabled requested GPS status
+ */
+ public static void gps(final Context context, final Address replyTo, final boolean enabled) {
+ Secure.setLocationProviderEnabled(context.getContentResolver(), LocationManager.GPS_PROVIDER, enabled);
+ if(enabled)
+ Utils.sendMessage(context, replyTo, enabling_gps);
+ else
+ Utils.sendMessage(context, replyTo, disabling_gps);
+ }
+
+ /**
+ * Get the Google location (aka network location) state.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ */
+ public static void glocation(final Context context, final Address replyTo){
+ if(Secure.isLocationProviderEnabled(context.getContentResolver(), LocationManager.NETWORK_PROVIDER))
+ Utils.sendMessage(context, replyTo, network_location_on);
+ else
+ Utils.sendMessage(context, replyTo, network_location_off);
+ }
+
+ /**
+ * Set the Google location (aka network location) state.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param enabled requested Google location state
+ */
+ public static void glocation(final Context context, final Address replyTo, final boolean enabled) {
+ Secure.setLocationProviderEnabled(context.getContentResolver(), LocationManager.NETWORK_PROVIDER, enabled);
+ if(enabled)
+ Utils.sendMessage(context, replyTo, enabling_network_location);
+ else
+ Utils.sendMessage(context, replyTo, disabling_network_location);
+ }
+
+ /**
+ * Reboot the phone.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param reason reboot reason
+ *
+ * @see PowerManager#reboot(String)
+ */
+ public static void reboot(final Context context, final Address replyTo, final @Nullable String reason) {
+ final PowerManager pm=(PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ Utils.sendMessage(context, replyTo, rebooting);
+ pm.reboot(reason);
+ }
+
+ /**
+ * Cancel a notification.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param id notification ID
+ */
+ public static void notify(final Context context, final Address replyTo, final int id) {
+ final NotificationManager man=(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ man.cancel(id);
+ Utils.sendMessage(context, replyTo, notification_canceled);
+ }
+
+ /**
+ * Show a notification.
+ *
+ * @param context Context instance
+ * @param replyTo reply Address
+ * @param id notification ID
+ * @param title notificationO title
+ * @param text notification text
+ */
+ public static void notify(final Context context, final Address replyTo, final int id, final String title, final String text) {
+ final NotificationManager man=(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ man.notify(id, new NotificationCompat.Builder(context).
+ setContentTitle(title).
+ setContentText(text).
+ setSmallIcon(android.R.drawable.stat_notify_sync_noanim).
+ build());
+ Utils.sendMessage(context, replyTo, notification_shown);
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * BroadcastReceiver that receives appwide local broadcasts.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class LocalBroadcastReceiver extends BroadcastReceiver {
+ /**
+ * Broadcast action: the poll alarm has fired, the app should poll the server
+ */
+ public static final String ACTION_POLL_ALARM="ro.ieval.fonbot.SystemEventReceiver.ACTION_POLL_ALARM";
+
+ @Override
+ public void onReceive(final @Nullable Context context, final @Nullable Intent intent) {
+ if(intent==null)
+ return;
+
+ final String action=intent.getAction();
+ if(action.equals(LocalBroadcastReceiver.ACTION_POLL_ALARM))
+ new PollServerAsyncTask().execute();
+ }
+
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import ro.ieval.fonbot.Address.Protocol;
+import android.os.AsyncTask;
+import android.util.Log;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * AsyncTask that polls the server for pending commands and executes any commands found.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class PollServerAsyncTask extends AsyncTask<Void, Void, List<PollServerAsyncTask.Command>> {
+ /**
+ * POJO that represents a command in a basic form: a command name, a list of arguments and a reply address, all of them being strings
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ final class Command{
+ /**
+ * The command name
+ */
+ final String command;//NOPMD no confusion here
+ /**
+ * The list of command arguments
+ */
+ final String[] args;
+ /**
+ * The reply address, excluding the protocol
+ */
+ final String replyto;
+
+ /**
+ * Construct a Command from its three parts
+ *
+ * @param command the command name
+ * @param args the command arguments
+ * @param replyto the reply address
+ */
+ Command(final String command, final String[] args, final String replyto) {//NOPMD array is immutable
+ this.command=command;
+ this.args=args;
+ this.replyto=replyto;
+ }
+ }
+
+ @Override
+ protected List<Command> doInBackground(final @Nullable Void... params) {
+ Log.d(getClass().getName(), "Polling server");
+ final List<Command> commands=new ArrayList<Command>(10);
+ try {
+ final URL url=new URL("http://ieval.ro:7777/get");
+ final HttpURLConnection conn=(HttpURLConnection) url.openConnection();
+ conn.setRequestProperty("X-ID", FonBotApplication.instance.regID);
+ conn.connect();
+ final byte[] buf=new byte[4096*1024];
+ Log.d(getClass().getName(), "Server poll got response code "+conn.getResponseCode()+" and message "+conn.getResponseMessage());
+ if(conn.getResponseCode()!=200)
+ return commands;
+ conn.getInputStream().read(buf);
+ final JSONArray array=new JSONArray(new String(buf));
+ final List<String> list=new ArrayList<String>();
+ for(int i=0;i<array.length();i++){
+ final JSONObject object=array.getJSONObject(i);
+
+ for(int j=0;object.has("arg"+j);j++)
+ list.add(object.getString("arg"+j));
+
+ commands.add(new Command(//NOPMD command changes for each JSON object
+ toNonNull(object.getString("command")),
+ toNonNull(list.toArray(new String[list.size()])),
+ toNonNull(object.getString("replyto"))));
+ list.clear();
+ }
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ if(commands==null)
+ throw new AssertionError("commands is null in PollServerAsyncTask#doInBackground");//silence Eclipse bug
+ return commands;
+ }
+
+ @Override
+ protected void onPostExecute(final @Nullable List<Command> commands) {
+ if(commands==null)
+ return;
+ for (Command command : commands) {
+ Log.d(getClass().getName(), "Poll got command "+command.command+" with "+((command.args.length==0)?"no args":"args "+Utils.join(
+ " ",toNonNull(command.args))));
+ Utils.processCommand(
+ toNonNull(FonBotApplication.instance),
+ toNonNull(command.command),
+ toNonNull(command.args),
+ new Address(toNonNull(Protocol.HTTP), command.replyto));//NOPMD address depends on command
+ }
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.Utils.MessageType;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * BroadcastReceiver that receives various broadcasts sent by the sistem
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class ProtectedBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(@Nullable final Context context, @Nullable final Intent intent) {
+ if(context==null || intent==null)
+ return;
+
+ final String action=intent.getAction();
+
+ if(action.equals(Intent.ACTION_BATTERY_LOW)){
+ Utils.sendMessage(context, toNonNull(MessageType.BATTERY),
+ toNonNull(context.getString(battery_low)));
+ Heavy.describeBatteryLevel(context, null, toNonNull(MessageType.BATTERY));
+ } else if(action.equals(Intent.ACTION_BATTERY_OKAY)){
+ Utils.sendMessage(context, toNonNull(MessageType.BATTERY),
+ toNonNull(context.getString(battery_okay)));
+ Heavy.describeBatteryLevel(context, null, toNonNull(MessageType.BATTERY));
+ }
+ }
+
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.content.Context;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * UncaughtExceptionHandler that sends exceptions to the server.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+final class RemoteCrashdumpHandler implements UncaughtExceptionHandler {
+ /** Context instance */
+ private final Context context;
+
+ /**
+ * Constructs a RemoteCrashdumpHandler with a given Context instance.
+ *
+ * @param context Context instance
+ */
+ public RemoteCrashdumpHandler(final Context context) {
+ this.context=context;
+ }
+
+ @Override
+ public void uncaughtException(final @Nullable Thread thread, final @Nullable Throwable ex) {
+ if(ex==null)
+ return;
+
+ final ByteArrayOutputStream baos=new ByteArrayOutputStream(16384);
+ final PrintStream pw=new PrintStream(baos);
+ ex.printStackTrace(pw);
+ pw.close();
+
+ new SendHttpMessageAsyncTask(toNonNull(Arrays.asList(
+ new Header("X-Action", "CRASHDUMP")
+ )), toNonNull(context), toNonNull(baos.toByteArray())).execute();
+
+ try {
+ baos.close();
+ } catch (IOException e) {//NOPMD close exceptions are unimportant
+ //ignored
+ }
+ }
+
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Collection;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.util.Log;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * AsyncTask that sends a HTTP request to the server and broadcasts back the response message & response code
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class SendHttpMessageAsyncTask extends AsyncTask<String, Void, String> {
+ /**
+ * Extra: the response message
+ */
+ public static final String EXTRA_RESPONSE_MESSAGE="response_message";
+ /**
+ * Extra: the response code
+ */
+ public static final String EXTRA_RESPONSE_CODE="response_code";
+
+ /**
+ * List of extra request headers.
+ */
+ private final Collection<Header> headers;
+ /**
+ * Action to send back in the response broadcast
+ */
+ private final String broadcast;
+ /**
+ * Context instance used by this class
+ */
+ private final Context context;
+ /**
+ * Data to send to the server
+ */
+ private final byte[] data;
+ /**
+ * Response code from the server
+ */
+ private int responseCode;
+
+ /**
+ * Constructs a SendHttpMessageAsyncTask which sends a string-based message and does not broadcast the response.
+ *
+ * @param headers the extra headers
+ * @param context the context instance
+ */
+ public SendHttpMessageAsyncTask(final Collection<Header> headers, final Context context){
+ super();
+ this.headers=headers;
+ this.broadcast=null;//NOPMD final field
+ this.context=context;
+ this.data=null;//NOPMD final field
+ }
+
+ /**
+ * Constructs a SendHttpMessageAsyncTask which sends a binary message and does not broadcast the response.
+ *
+ * @param headers the extra headers
+ * @param context the context instance
+ * @param data the message to send
+ */
+ public SendHttpMessageAsyncTask(final Collection<Header> headers, final Context context, final byte[] data){//NOPMD array is supposed to be immutable.
+ super();
+ this.headers=headers;
+ this.broadcast=null;//NOPMD final field
+ this.context=context;
+ this.data=data;
+ }
+
+ /**
+ * Constructs a SendHttpMessageAsyncTask which sends a string-based message and broadcasts the response.
+ *
+ * @param headers the extra headers
+ * @param broadcast the broadcast to send
+ * @param context the context instance
+ */
+ public SendHttpMessageAsyncTask(final Collection<Header> headers, final String broadcast, final Context context){
+ super();
+ this.headers=headers;
+ this.broadcast=broadcast;
+ this.context=context;
+ this.data=null;//NOPMD final field
+ }
+
+ @Override
+ protected String doInBackground(final @Nullable String... args) {
+ if(args==null)
+ return "";
+
+ final byte[] msg;
+ if(data==null)
+ msg=Utils.join(" ", args).getBytes();
+ else
+ msg=data;
+
+ try {
+ final URL url=new URL("http://ieval.ro:7777/");
+ final HttpURLConnection conn=(HttpURLConnection) url.openConnection();
+ conn.setDoOutput(true);
+ conn.setFixedLengthStreamingMode(msg.length);
+ conn.setRequestProperty("X-ID", FonBotApplication.instance.regID);
+ for (Header header : headers)
+ conn.setRequestProperty(header.name, header.value);
+ conn.connect();
+ final OutputStream stream=conn.getOutputStream();
+ stream.write(msg);
+ stream.close();
+ Log.d(getClass().getName(),"HTTP Response: "+conn.getResponseCode()+" "+conn.getResponseMessage());
+ conn.disconnect();
+ final String message=conn.getResponseMessage();
+ responseCode=conn.getResponseCode();
+ if(message==null)
+ return toNonNull(context.getString(no_response_returned_from_server));
+ return Utils.parseHttpMessage(message);
+ } catch (Exception e) {
+ return toNonNull(context.getString(connection_error));
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final @Nullable String result) {
+ super.onPostExecute(result);
+ if(broadcast!=null){
+ final Intent intent=new Intent(broadcast);
+ intent.putExtra(EXTRA_RESPONSE_MESSAGE, result);
+ intent.putExtra(EXTRA_RESPONSE_CODE, responseCode);
+ context.sendBroadcast(intent);
+ }
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.*;
+
+import static ro.ieval.fonbot.Utils.toNonNull;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.Address.Protocol;
+import ro.ieval.fonbot.Utils.MessageType;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.preference.PreferenceManager;
+import android.telephony.SmsMessage;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * BroadcastReceiver that receives SMSes. It processes command smses and sends {@link MessageType#SMS} notifications.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class SmsReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(@Nullable final Context context, @Nullable final Intent intent) {
+ if(intent==null||context==null)
+ return;
+
+ final Object[] pdus=(Object[]) intent.getExtras().get("pdus");
+ for (Object pdu : pdus) {
+ final SmsMessage sms=SmsMessage.createFromPdu((byte[]) pdu);
+ final String originAddress=sms.getOriginatingAddress();
+ if(sms.getMessageBody() == null || originAddress == null)
+ return;
+
+ final String name=Utils.callerId(context, originAddress);
+ if(name==null)
+ Utils.sendMessage(context, toNonNull(MessageType.SMS),
+ toNonNull(context.getString(sms_received_fmt,
+ originAddress, sms.getMessageBody())));
+ else
+ Utils.sendMessage(context, toNonNull(MessageType.SMS),
+ toNonNull(context.getString(sms_received_fmt,
+ originAddress+" ("+name+")",sms.getMessageBody())));
+
+ final String[] lines=sms.getMessageBody().split("\n");
+ final String password = PreferenceManager.getDefaultSharedPreferences(context).getString("smspassword","");
+ if(password==null||password.length()==0)
+ return;
+
+ if(lines.length==0 || !lines[0].equals(password))
+ return;
+
+ final Address address=new Address(toNonNull(Protocol.SMS), originAddress);//NOPMD variable depends on originAddress
+ for (int i = 1; i < lines.length; i++) {
+ final String[] words=Utils.shellwords(toNonNull(lines[i]));
+ final String[] args=new String[words.length-1];//NOPMD variable size depends on words.length
+ System.arraycopy(words, 1, args, 0, args.length);
+
+ Utils.processCommand(context, toNonNull(words[0]), args, toNonNull(address));
+ }
+
+ abortBroadcast();
+ }
+ }
+
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telephony.SmsManager;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * BroadcastReceiver that receives SMS status broadcasts, notifying the user.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class SmsStatusReceiver extends BroadcastReceiver {
+ /**
+ * Formats a SMS message intent into a human-readable string
+ *
+ * @param intent the SMS message
+ * @return the string representation of the SMS
+ */
+ private static String describeSMS(final Intent intent){
+ return " ("+"To: "+intent.getStringExtra(EXTRA_DESTINATION)+
+ ", Part: "+intent.getIntExtra(EXTRA_PART, 1)+" of "+intent.getIntExtra(EXTRA_TOTAL, 1)+")";
+ }
+
+ /**
+ * Broadcast action: SMS sent
+ */
+ public static final String SENT_ACTION="ro.ieval.fonbot.SmsStatusReceiver.SENT";
+ /**
+ * Broadcast action: SMS delivered
+ */
+ public static final String DELIVERED_ACTION="ro.ieval.fonbot.SmsStatusReceiver.DELIVERED";
+ /**
+ * Extra: SMS destination (phone number)
+ */
+ public static final String EXTRA_DESTINATION="destination";
+ /**
+ * Extra: SMS part number
+ */
+ public static final String EXTRA_PART="part";
+ /**
+ * Extra: SMS part count
+ */
+ public static final String EXTRA_TOTAL="total";
+ /**
+ * Extra: Notification address
+ */
+ public static final String EXTRA_REPLY_TO="reply_to";
+
+ @Override
+ public void onReceive(@Nullable final Context context, @Nullable final Intent intent) {
+ if(intent==null||context==null)
+ return;
+ final Address replyTo=new Address(Utils.toNonNull(intent.getStringExtra(EXTRA_REPLY_TO)));
+ if(intent.getAction().startsWith(SENT_ACTION))
+ switch(getResultCode()){
+ case Activity.RESULT_OK:
+ Utils.sendMessage(context, replyTo, "SMS sent"+describeSMS(intent));
+ break;
+ case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
+ Utils.sendMessage(context, replyTo, "SMS not sent: Generic failure"+describeSMS(intent));
+ break;
+ case SmsManager.RESULT_ERROR_NO_SERVICE:
+ Utils.sendMessage(context, replyTo, "SMS not sent: No service"+describeSMS(intent));
+ break;
+ case SmsManager.RESULT_ERROR_NULL_PDU:
+ Utils.sendMessage(context, replyTo, "SMS not sent: Null PDU"+describeSMS(intent));
+ break;
+ case SmsManager.RESULT_ERROR_RADIO_OFF:
+ Utils.sendMessage(context, replyTo, "SMS not sent: Radio off"+describeSMS(intent));
+ break;
+ }
+ else if(intent.getAction().startsWith(DELIVERED_ACTION))
+ switch(getResultCode()){
+ case Activity.RESULT_OK:
+ Utils.sendMessage(context, replyTo, "SMS delivered"+describeSMS(intent));
+ break;
+ case Activity.RESULT_CANCELED:
+ Utils.sendMessage(context, replyTo, "SMS not delivered"+describeSMS(intent));
+ break;
+ }
+ }
+}
--- /dev/null
+package ro.ieval.fonbot;
+
+import static ro.ieval.fonbot.R.string.cannot_parse_count;
+import static ro.ieval.fonbot.R.string.cannot_parse_interval;
+import static ro.ieval.fonbot.R.string.cannot_parse_min_distance;
+import static ro.ieval.fonbot.R.string.cannot_parse_min_time;
+import static ro.ieval.fonbot.R.string.cannot_parse_port;
+import static ro.ieval.fonbot.R.string.cannot_parse_provider_allowed_values_are;
+import static ro.ieval.fonbot.R.string.command_disabled;
+import static ro.ieval.fonbot.R.string.could_not_parse_argument_allowed_values_are;
+import static ro.ieval.fonbot.R.string.could_not_parse_ms;
+import static ro.ieval.fonbot.R.string.error_while_processing_command;
+import static ro.ieval.fonbot.R.string.invalid_length_allowed_values_are;
+import static ro.ieval.fonbot.R.string.invalid_ringer_mode_valid_values_are;
+import static ro.ieval.fonbot.R.string.location_tracking_is_active;
+import static ro.ieval.fonbot.R.string.messagetype_should_be_one_of;
+import static ro.ieval.fonbot.R.string.no_such_command_command_list;
+import static ro.ieval.fonbot.R.string.notification_disabled;
+import static ro.ieval.fonbot.R.string.notification_enabled;
+import static ro.ieval.fonbot.R.string.ringing;
+import static ro.ieval.fonbot.R.string.security_exception;
+import static ro.ieval.fonbot.R.string.the_polling_service_is_running;
+import static ro.ieval.fonbot.R.string.the_second_argument_to_wipe_must_be;
+import static ro.ieval.fonbot.R.string.unknown_command;
+import static ro.ieval.fonbot.R.string.wipetype_should_be_one_of;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import ro.ieval.fonbot.Address.Protocol;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.location.LocationManager;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.preference.PreferenceManager;
+import android.provider.ContactsContract.PhoneLookup;
+import android.telephony.SmsManager;
+import android.util.Log;
+import android.widget.Toast;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Utility functions and enums used in various places.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+public final class Utils {
+ /**
+ * Enum of all FonBot commands.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ @SuppressWarnings("javadoc")
+ public static enum Command{
+ TOAST, ECHO, SMS, FLASH, WIFI,
+ BLUETOOTH, DIAL, RING, SPEAK, VIBRATE,
+ DIALOG, LOCATION, NOLOCATION, RINGER, NCFILE,
+ PHOTO, SETNOTIFICATION, DELNOTIFICATION, SETPASSWORD, HELP,
+ WIPE, LOCK, VIEW, PLAY, PAUSE,
+ NEXT, PREV, BATT, CALLLOG, SMSLOG,
+ LS, RM, CONTACTS, DISABLE, ENABLE,
+ POLL, HANGUP, ANSWER, LAUNCH, DATA,
+ GPS, GLOCATION, REBOOT, SHUTDOWN, NOTIFY
+ }
+
+ /**
+ * Enum of all message types. Each message type is a kind of notification which can be enabled/disabled by the user and directed to a certain address.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ *
+ */
+ public static enum MessageType{
+ /** SMS notifications */
+ SMS,
+ /** Phone state (idle, offhook or ringing) notifications */
+ PHONE_STATE,
+ /** Lockscreen failed password notifications */
+ WATCH_LOGIN,
+ /** Device admin enable/disable notifications */
+ ADMIN,
+ /** Coarse battery status (low battery, ok battery) notifications */
+ BATTERY,
+ /** Fine battery status notifications */
+ BATTERY_CHANGED,
+ /** Headset plug/unplug notifications */
+ HEADSET
+ }
+
+ /**
+ * Enum of wipe types. The two defined types are the data wipe (which does not include the SD card) and the full wipe (which includes the SD card).
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ public static enum WipeType{
+ /** Factory reset the phone, without touching the SD card */
+ DATA,
+ /** Factory reset the phone and wipe the SD card too */
+ FULL
+ }
+
+ /**
+ * Enum of ringer modes.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ public static enum RingerMode{
+ /** Sound is on */
+ NORMAL,
+ /** Sound is off, vibrate is on */
+ VIBRATE,
+ /** Sound is off, vibrate is off */
+ SILENT
+ }
+
+ /**
+ * Enum of location providers
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ public static enum LocationProvider{
+ /** The network location provider */
+ NETWORK,
+ /** The GPS location provider */
+ GPS
+ }
+
+ /**
+ * Enum of toast lengths.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ private static enum ToastLength{
+ /** Long toast */
+ LONG,
+ /** Short toast */
+ SHORT
+ }
+
+ /**
+ * Enum of the values on and off. Used for boolean arguments.
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ @SuppressWarnings("javadoc")
+ private static enum OnOff{
+ ON, OFF
+ }
+
+ /**
+ * Enum of ongoing event types
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ public static enum OngoingEvent{
+ /** Location tracking is active. Registered by {@link Command#LOCATION}, unregistered by {@link Command#NOLOCATION} */
+ LOCATION(location_tracking_is_active),
+ /** The phone is ringing. Registered/unregistered by {@link Command#RING} */
+ RING(ringing),
+ /** The polling alarm is on. Registered/unregistered by {@link Command#POLL} */
+ POLL(the_polling_service_is_running);
+
+ /** String resource: the event description */
+ public final int resource;
+
+ /**
+ * Constructs an OngoingEvent from its event description.
+ *
+ * @param resource the event description
+ */
+ private OngoingEvent(final int resource) {
+ this.resource=resource;
+ }
+ }
+
+ /** Confirmation string for the {@link Command#WIPE WIPE} command. */
+ public static final String WIPE_CONFIRM_STRING="I am aware this cannot be undone";
+
+ /**
+ * Converts a Nullable object into a NonNull one.
+ *
+ * @param object the Nullable object to convert
+ * @return a NonNull object equivalent to the Nullable parameter
+ * @throws AssertionError if the given object is null
+ */
+ public static <T> T toNonNull(@Nullable T object) throws AssertionError{
+ if(object==null){
+ Log.wtf(Utils.class.getName(), "toNonNull called with null");
+ throw new AssertionError("Log.wtf did not terminate the process");
+ }
+ return object;
+ }
+
+ /**
+ * Join an array of Objects with elements separated by a separator into a single string.
+ *
+ * @param separator the separator
+ * @param offset the offset into the array
+ * @param args the array of Objects to join
+ * @return the joined string
+ */
+ public static String join(final String separator,final int offset,final Object[] args){
+ final StringBuilder sb=new StringBuilder(240);
+ sb.append(args[offset]);
+ for (int i = offset+1; i < args.length; i++) {
+ sb.append(separator);
+ sb.append(args[i]);
+ }
+ return toNonNull(sb.toString());
+ }
+
+ /**
+ * Join an array of Objects with elements separated by a separator into a single string.
+ *
+ * @param separator the separator
+ * @param args the array of objects to join
+ * @return the joined string
+ */
+ public static String join(final String separator, final Object[] args){
+ return join(separator,0,args);
+ }
+
+ /**
+ * Send a notification to the user.
+ *
+ * @param context Context instance
+ * @param type notification type
+ * @param resource String resource for the message text
+ */
+ public static void sendMessage(final Context context, final MessageType type, final int resource){
+ sendMessage(context, type, toNonNull(context.getString(resource)));
+ }
+
+ /**
+ * Send a notification to the user.
+ *
+ * @param context Context instance
+ * @param type notification type
+ * @param resource String resource for the message text
+ * @param args format parameters for the resource
+ */
+ public static void sendMessage(final Context context, final MessageType type, final int resource, final Object... args){
+ sendMessage(context, type, toNonNull(context.getString(resource, args)));
+ }
+
+ /**
+ * Send a message to a certain Address.
+ *
+ * @param context Context instance
+ * @param address destination Address
+ * @param resource String resource for the message text
+ */
+ public static void sendMessage(final Context context, final Address address, final int resource){
+ sendMessage(context, address, toNonNull(context.getString(resource)));
+ }
+
+ /**
+ * Send a message to a certain Address.
+ *
+ * @param context Context instance
+ * @param address destination Address
+ * @param resource String resource for the message text
+ * @param args format parameters for the resource
+ */
+ public static void sendMessage(final Context context, final Address address, final int resource, final Object... args){
+ sendMessage(context, address, toNonNull(context.getString(resource, args)));
+ }
+
+ /**
+ * Send a notification to the user.
+ *
+ * @param context Context instance
+ * @param type notification type
+ * @param msg the notification text
+ */
+ public static void sendMessage(final Context context, final MessageType type,final String msg){
+ final SharedPreferences sp=PreferenceManager.getDefaultSharedPreferences(context);
+ final String address=sp.getString(type.toString(),null);
+ if(address==null)
+ return;
+ sendMessage(context, new Address(address), msg);
+ }
+
+ /**
+ * Send a message to a certain Address.
+ *
+ * @param context Context instance
+ * @param address destination Address
+ * @param message the message text
+ */
+ public static void sendMessage(final Context context, final Address address, final String message){
+ switch(address.protocol){
+ case HTTP:
+ new SendHttpMessageAsyncTask(toNonNull(Arrays.asList(
+ new Header("X-Action", "SEND "+address.data))), context).execute(message);
+ break;
+
+ case SMS:
+ SmsManager.getDefault().sendTextMessage(address.data, null, message, null, null);
+ break;
+
+ case LOCAL:
+ final Intent intent = new Intent(FonBotLocalActivity.RESPONSE_RECEIVED_ACTION);
+ intent.putExtra(FonBotLocalActivity.EXTRA_RESPONSE, message);
+ context.sendBroadcast(intent);
+ break;
+
+ case NULL:
+ break;//blackhole
+ }
+ }
+
+ /**
+ * Parses a HTTP response message of the form <code>(part1) part2</code>, returning <code>part2</code>.
+ *
+ * @param message the raw HTTP response message
+ * @return the extracted part
+ */
+ public static String parseHttpMessage(final String message){
+ final int indexOfParen=message.indexOf(')');
+ if(indexOfParen==-1)
+ return message;
+ return toNonNull(message.substring(indexOfParen+2));
+ }
+
+ /**
+ * Splits a string into words.
+ *
+ * @param string the string
+ * @return an array of words
+ */
+ public static String[] shellwords(final String string){
+ return toNonNull(string.split("[ ]+(?=([^\"]*\"[^\"]*\")*[^\"]*$)"));//TODO: Make this handle backslash escapes
+ }
+
+ /**
+ * Returns the name associated with a phone number.
+ *
+ * @param context Context instance
+ * @param number phone number to search for
+ * @return the name associated with this number, or null if the number was not found in the contacts.
+ */
+ public static @Nullable String callerId(final Context context, final String number){
+ final Cursor cursor=context.getContentResolver().query(
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
+ new String[]{PhoneLookup.DISPLAY_NAME},
+ null,
+ null,
+ null);
+
+ if(cursor.moveToFirst()){
+ final String name=cursor.getString(0);
+ cursor.close();
+ return name;
+ }
+ cursor.close();
+ return null;
+ }
+
+ /**
+ * Registers an ongoing event.
+ *
+ * @param context Context instance
+ * @param event event to register
+ */
+ public static void registerOngoing(final Context context, final OngoingEvent event){
+ final Intent intent=new Intent(context, FonBotMainService.class);
+ intent.setAction(FonBotMainService.ACTION_PUT_ONGOING);
+ intent.putExtra(FonBotMainService.EXTRA_ONGOING_ID, event.ordinal());
+ context.startService(intent);
+ }
+
+ /**
+ * Unregisters an ongoing event
+ *
+ * @param context Context instance
+ * @param event event to unregister
+ */
+ public static void unregisterOngoing(final Context context, final OngoingEvent event){
+ final Intent intent=new Intent(context, FonBotMainService.class);
+ intent.setAction(FonBotMainService.ACTION_DELETE_ONGOING);
+ intent.putExtra(FonBotMainService.EXTRA_ONGOING_ID, event.ordinal());
+ context.startService(intent);
+ }
+
+ /**
+ * Executes a given command
+ *
+ * @param context Context instance
+ * @param cmd Command to execute
+ * @param args arguments for the command
+ * @param replyTo Address to send replies to
+ */
+ private static void processCommand(final Context context, final Command cmd,final String[] args,final Address replyTo){
+ if(Heavy.isCommandDisabled(context, cmd)){
+ sendMessage(context, replyTo, command_disabled);
+ return;
+ }
+
+ switch(cmd){
+ case TOAST:
+ if(args.length < 1 || args.length > 2){
+ Heavy.help(context, replyTo, toNonNull(Command.TOAST));
+ break;
+ }
+
+ if(args.length==1)
+ Heavy.toast(context, replyTo, toNonNull(args[0]));
+ else {
+ try {
+ switch(ToastLength.valueOf(args[1].toUpperCase(Locale.ENGLISH))){
+ case LONG:
+ Heavy.toast(context, replyTo, toNonNull(args[0]), Toast.LENGTH_LONG);
+ break;
+ case SHORT:
+ Heavy.toast(context, replyTo, toNonNull(args[0]), Toast.LENGTH_SHORT);
+ }
+ } catch(IllegalArgumentException e){
+ sendMessage(context, replyTo, invalid_length_allowed_values_are, join(", ",toNonNull(ToastLength.values())));
+ }
+ }
+ break;
+
+ case ECHO:
+ if(args.length==0){
+ Heavy.help(context, replyTo, toNonNull(Command.ECHO));
+ break;
+ }
+ sendMessage(context, replyTo, join(" ",args));
+ break;
+
+ case SMS:
+ if(args.length < 2){
+ Heavy.help(context, replyTo, toNonNull(Command.SMS));
+ break;
+ }
+ Heavy.sms(context, replyTo, toNonNull(args[0]), join(" ", 1, args));
+ break;
+
+ case FLASH:
+ if(args.length != 1){
+ Heavy.help(context, replyTo, toNonNull(Command.FLASH));
+ break;
+ }
+
+ try {
+ Heavy.flash(context, replyTo, OnOff.valueOf(args[0].toUpperCase(Locale.ENGLISH)) == OnOff.ON);
+ } catch(IllegalArgumentException e) {
+ sendMessage(context, replyTo, could_not_parse_argument_allowed_values_are, join(", ", toNonNull(OnOff.values())));
+ }
+ break;
+
+ case WIFI:
+ if(args.length>1){
+ Heavy.help(context, replyTo, toNonNull(Command.WIFI));
+ break;
+ }
+ if(args.length==0)
+ Heavy.wifi(context, replyTo);
+ else {
+ try {
+ Heavy.wifi(context, replyTo, OnOff.valueOf(args[0].toUpperCase(Locale.ENGLISH)) == OnOff.ON);
+ } catch(IllegalArgumentException e) {
+ sendMessage(context, replyTo, could_not_parse_argument_allowed_values_are, join(", ", toNonNull(OnOff.values())));
+ }
+ }
+ break;
+
+ case BLUETOOTH:
+ if(args.length>1){
+ Heavy.help(context, replyTo, toNonNull(Command.BLUETOOTH));
+ break;
+ }
+ if(args.length==0)
+ Heavy.bluetooth(context, replyTo);
+ else {
+ try {
+ Heavy.bluetooth(context, replyTo, OnOff.valueOf(args[0].toUpperCase(Locale.ENGLISH)) == OnOff.ON);
+ } catch(IllegalArgumentException e) {
+ sendMessage(context, replyTo, could_not_parse_argument_allowed_values_are, join(", ", toNonNull(OnOff.values())));
+ }
+ }
+ break;
+
+ case DIAL:
+ if(args.length!=1){
+ Heavy.help(context, replyTo, toNonNull(Command.DIAL));
+ break;
+ }
+ Heavy.dial(context, replyTo, toNonNull(args[0]));
+ break;
+
+ case RING:
+ if(args.length>1){
+ Heavy.help(context, replyTo, toNonNull(Command.RING));
+ break;
+ }
+ if(args.length==0)
+ Heavy.ring(context, replyTo);
+ else {
+ try {
+ Heavy.ring(context, replyTo, OnOff.valueOf(args[0].toUpperCase(Locale.ENGLISH)) == OnOff.ON);
+ } catch(IllegalArgumentException e){
+ sendMessage(context, replyTo, could_not_parse_argument_allowed_values_are, join(", ", toNonNull(OnOff.values())));
+ }
+ }
+ break;
+
+ case SPEAK:
+ if(args.length==0){
+ Heavy.help(context, replyTo, toNonNull(Command.SPEAK));
+ break;
+ }
+ Heavy.speak(context, replyTo, join(" ",args));
+ break;
+
+ case VIBRATE:
+ if(args.length!=1){
+ Heavy.help(context, replyTo, toNonNull(Command.VIBRATE));
+ break;
+ }
+ final long ms;
+ try{
+ ms=Long.parseLong(args[0]);
+ } catch(NumberFormatException e){
+ sendMessage(context, replyTo, could_not_parse_ms);
+ break;
+ }
+ Heavy.vibrate(context, replyTo, ms);
+ break;
+
+ case DIALOG://TODO: Should add an edittext
+ if(args.length<1){
+ Heavy.help(context, replyTo, toNonNull(Command.DIALOG));
+ break;
+ }
+ final String[] buttons=new String[args.length-1];
+ System.arraycopy(args,1,buttons,0,buttons.length);
+ Heavy.dialog(context, replyTo, toNonNull(args[0]), buttons);
+ break;
+
+ case LOCATION:
+ if(args.length>3||args.length<1){
+ Heavy.help(context, replyTo, toNonNull(Command.LOCATION));
+ break;
+ }
+
+ final @NonNull String provider;
+ try{
+ switch(LocationProvider.valueOf(args[0].toUpperCase(Locale.ENGLISH))){
+ case GPS:
+ provider=toNonNull(LocationManager.GPS_PROVIDER);
+ break;
+ case NETWORK:
+ default:
+ provider=toNonNull(LocationManager.NETWORK_PROVIDER);
+ break;
+ }
+ } catch(IllegalArgumentException e){
+ sendMessage(context, replyTo, cannot_parse_provider_allowed_values_are, join(", ",toNonNull(LocationProvider.values())));
+ break;
+ }
+
+ final long minTime;
+ final float minDistance;
+
+ if(args.length>1)
+ try{
+ minTime=Long.parseLong(args[1]);
+ } catch (NumberFormatException e){
+ sendMessage(context, replyTo, cannot_parse_min_time);
+ break;
+ }
+ else
+ minTime=500;
+
+ if(args.length>2)
+ try{
+ minDistance=Float.parseFloat(args[2]);
+ } catch (NumberFormatException e){
+ sendMessage(context, replyTo, cannot_parse_min_distance);
+ break;
+ }
+ else
+ minDistance=0;
+ Heavy.location(context, replyTo, provider, minTime, minDistance);
+ break;
+
+ case NOLOCATION:
+ Heavy.nolocation(context, replyTo);
+ break;
+
+ case RINGER:
+ if(args.length>1){
+ Heavy.help(context, replyTo, toNonNull(Command.RINGER));
+ break;
+ }
+ if(args.length==0)
+ Heavy.ringer(context, replyTo);
+ else{
+ try{
+ final RingerMode rm=RingerMode.valueOf(args[0].toUpperCase(Locale.ENGLISH));
+ switch(rm){
+ case NORMAL:
+ Heavy.ringer(context, replyTo, AudioManager.RINGER_MODE_NORMAL);
+ break;
+ case VIBRATE:
+ Heavy.ringer(context, replyTo, AudioManager.RINGER_MODE_VIBRATE);
+ break;
+ case SILENT:
+ Heavy.ringer(context, replyTo, AudioManager.RINGER_MODE_SILENT);
+ break;
+ }
+ } catch (IllegalArgumentException e){
+ Utils.sendMessage(context, replyTo, invalid_ringer_mode_valid_values_are, join(", ",toNonNull(RingerMode.values())));
+ }
+ }
+ break;
+
+ case NCFILE:
+ if(args.length!=3){
+ Heavy.help(context, replyTo, toNonNull(Command.NCFILE));
+ break;
+ }
+
+ final int ncfilePort;
+ try{
+ ncfilePort=Integer.parseInt(args[2]);
+ } catch (NumberFormatException e){
+ sendMessage(context, replyTo, cannot_parse_port);
+ break;
+ }
+ Heavy.ncfile(context, replyTo, toNonNull(args[0]), toNonNull(args[1]), ncfilePort);
+ break;
+
+ case PHOTO:
+ if(args.length!=2){
+ Heavy.help(context, replyTo, toNonNull(Command.PHOTO));
+ break;
+ }
+ final int photoPort;
+ try{
+ photoPort=Integer.parseInt(args[1]);
+ } catch (NumberFormatException e){
+ sendMessage(context, replyTo, cannot_parse_port);
+ break;
+ }
+ Heavy.photo(context, replyTo, toNonNull(args[0]), photoPort);
+ break;
+
+ case SETNOTIFICATION:
+ if(args.length!=1){
+ Heavy.help(context, replyTo, toNonNull(Command.SETNOTIFICATION));
+ break;
+ }
+
+ try{
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putString(MessageType.valueOf(args[0].toUpperCase(Locale.ENGLISH)).toString(), replyTo.toString())
+ .commit();
+ sendMessage(context, replyTo, notification_enabled);
+ } catch (IllegalArgumentException e){
+ sendMessage(context, replyTo, messagetype_should_be_one_of, join(", ",toNonNull(MessageType.values())));
+ break;
+ }
+
+ break;
+
+ case DELNOTIFICATION:
+ if(args.length!=1){
+ Heavy.help(context, replyTo, toNonNull(Command.DELNOTIFICATION));
+ break;
+ }
+
+ try{
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .remove(MessageType.valueOf(args[0].toUpperCase(Locale.ENGLISH)).toString())
+ .commit();
+ sendMessage(context, replyTo, notification_disabled);
+ } catch (IllegalArgumentException e){
+ sendMessage(context, replyTo, messagetype_should_be_one_of, join(", ",toNonNull(MessageType.values())));
+ break;
+ }
+
+
+ break;
+ case SETPASSWORD:
+ if(args.length > 1){
+ Heavy.help(context, replyTo, toNonNull(Command.SETPASSWORD));
+ break;
+ }
+
+ try{
+ if(args.length==0)
+ Heavy.setPassword(context, replyTo);
+ else
+ Heavy.setPassword(context, replyTo, toNonNull(args[0]));
+ } catch (SecurityException e){
+ sendMessage(context, replyTo, security_exception+e.getMessage());
+ }
+ break;
+
+ case WIPE:
+ if(args.length!=2){
+ Heavy.help(context, replyTo, toNonNull(Command.WIPE));
+ break;
+ }
+
+ if(!args[1].equalsIgnoreCase(WIPE_CONFIRM_STRING)){
+ sendMessage(context, replyTo, the_second_argument_to_wipe_must_be, WIPE_CONFIRM_STRING);
+ break;
+ }
+
+ try{
+ Heavy.wipe(context, toNonNull(WipeType.valueOf(args[0].toUpperCase(Locale.ENGLISH))));
+ } catch (IllegalArgumentException e){
+ sendMessage(context, replyTo, wipetype_should_be_one_of, join (", ",toNonNull(WipeType.values())));
+ } catch (SecurityException e){
+ sendMessage(context, replyTo, security_exception, e.getMessage());
+ }
+ break;
+
+ case LOCK:
+ try{
+ Heavy.lock(context, replyTo);
+ } catch (SecurityException e){
+ sendMessage(context, replyTo, security_exception, e.getMessage());
+ }
+ break;
+
+ case VIEW:
+ if(args.length!=1){
+ Heavy.help(context, replyTo, toNonNull(Command.VIEW));
+ break;
+ }
+
+ Heavy.view(context, replyTo, toNonNull(Uri.parse(args[0])));
+ break;
+
+ case PLAY:
+ Heavy.musicPlayerCommand(context, replyTo, "play");
+ break;
+
+ case PAUSE:
+ Heavy.musicPlayerCommand(context, replyTo, "pause");
+ break;
+
+ case NEXT:
+ Heavy.musicPlayerCommand(context, replyTo, "next");
+ break;
+
+ case PREV:
+ Heavy.musicPlayerCommand(context, replyTo, "previous");
+ break;
+
+ case BATT:
+ Heavy.batt(context, replyTo);
+ break;
+
+ case CALLLOG:
+ if (args.length > 1) {
+ Heavy.help(context, replyTo, toNonNull(Command.CALLLOG));
+ break;
+ }
+
+ if (args.length == 0)
+ Heavy.calllog(context, replyTo, 5);
+ else {
+ try {
+ Heavy.calllog(context, replyTo, Integer.parseInt(args[0]));
+ } catch (IllegalArgumentException e){
+ sendMessage(context, replyTo, cannot_parse_count);
+ }
+ }
+ break;
+
+ case SMSLOG:
+ if (args.length > 1) {
+ Heavy.help(context, replyTo, toNonNull(Command.SMSLOG));
+ break;
+ }
+
+ if (args.length == 0)
+ Heavy.smslog(context, replyTo, 5);
+ else {
+ try {
+ Heavy.smslog(context, replyTo, Integer.parseInt(args[0]));
+ } catch (IllegalArgumentException e) {
+ sendMessage(context, replyTo, cannot_parse_count);
+ }
+ }
+ break;
+
+ case HELP:
+ if(args.length != 1){
+ Heavy.help(context, replyTo, toNonNull(Command.HELP));
+ break;
+ }
+
+ try {
+ Heavy.help(context, replyTo, toNonNull(Command.valueOf(args[0].toUpperCase(Locale.ENGLISH))));
+ } catch (IllegalArgumentException e) {
+ sendMessage(context, replyTo, no_such_command_command_list, join(", ", toNonNull(Command.values())));
+ }
+ break;
+
+ case LS:
+ if(args.length != 1){
+ Heavy.help(context, replyTo, toNonNull(Command.LS));
+ break;
+ }
+
+ Heavy.ls(context, replyTo, toNonNull(args[0]));
+ break;
+
+ case RM:
+ if(args.length != 1){
+ Heavy.help(context, replyTo, toNonNull(Command.RM));
+ break;
+ }
+
+ Heavy.rm(context, replyTo, toNonNull(args[0]));
+ break;
+
+ case CONTACTS:
+ if(args.length != 1){
+ Heavy.help(context, replyTo, toNonNull(Command.CONTACTS));
+ break;
+ }
+
+ Heavy.contacts(context, replyTo, toNonNull(args[0]));
+ break;
+
+ case DISABLE:
+ if(replyTo.protocol != Protocol.LOCAL || args.length != 1){
+ Heavy.help(context, replyTo, toNonNull(Command.DISABLE));
+ break;
+ }
+
+ try{
+ Heavy.disable(context, replyTo, toNonNull(Command.valueOf(args[0].toUpperCase(Locale.ENGLISH))));
+ } catch (IllegalArgumentException e){
+ sendMessage(context, replyTo, no_such_command_command_list, join(", ", toNonNull(Command.values())));
+ }
+ break;
+
+ case ENABLE:
+ if(replyTo.protocol != Protocol.LOCAL || args.length != 1){
+ Heavy.help(context, replyTo, toNonNull(Command.ENABLE));
+ break;
+ }
+
+ try{
+ Heavy.enable(context, replyTo, toNonNull(Command.valueOf(args[0].toUpperCase(Locale.ENGLISH))));
+ } catch (IllegalArgumentException e){
+ sendMessage(context, replyTo, no_such_command_command_list, join(", ", toNonNull(Command.values())));
+ }
+ break;
+
+ case POLL:
+ if(args.length>1){
+ Heavy.help(context, replyTo, toNonNull(Command.POLL));
+ break;
+ }
+
+ if(args.length==0){
+ Heavy.poll(context, replyTo);
+ break;
+ }
+
+ final long interval;
+ try{
+ interval=Long.parseLong(args[0]);
+ } catch(NumberFormatException e){
+ sendMessage(context, replyTo, cannot_parse_interval);
+ break;
+ }
+
+ Heavy.poll(context, replyTo, interval);
+ break;
+
+ case HANGUP:
+ Heavy.hangup(context, replyTo);
+ break;
+
+ case ANSWER:
+ Heavy.answer(context, replyTo);
+ break;
+
+ case LAUNCH:
+ if(args.length!=1){
+ Heavy.help(context, replyTo, toNonNull(Command.LAUNCH));
+ break;
+ }
+ Heavy.launch(context, replyTo, toNonNull(args[0]));
+ break;
+
+ case DATA:
+ if(args.length>1){
+ Heavy.help(context, replyTo, toNonNull(Command.DATA));
+ break;
+ }
+
+ if(args.length==0){
+ Heavy.data(context, replyTo);
+ break;
+ }
+ try {
+ Heavy.data(context, replyTo, OnOff.valueOf(args[0].toUpperCase(Locale.ENGLISH)) == OnOff.ON);
+ } catch(IllegalArgumentException e) {
+ sendMessage(context, replyTo, could_not_parse_argument_allowed_values_are, join(", ", toNonNull(OnOff.values())));
+ }
+ break;
+
+ case GPS:
+ if(args.length>1){
+ Heavy.help(context, replyTo, toNonNull(Command.GPS));
+ break;
+ }
+
+ if(args.length==0){
+ Heavy.gps(context, replyTo);
+ break;
+ }
+
+ try {
+ Heavy.gps(context, replyTo, OnOff.valueOf(args[0].toUpperCase(Locale.ENGLISH)) == OnOff.ON);
+ } catch(IllegalArgumentException e) {
+ sendMessage(context, replyTo, could_not_parse_argument_allowed_values_are, join(", ", toNonNull(OnOff.values())));
+ }
+ break;
+
+ case GLOCATION:
+ if(args.length>1){
+ Heavy.help(context, replyTo, toNonNull(Command.GLOCATION));
+ break;
+ }
+
+ if(args.length==0){
+ Heavy.glocation(context, replyTo);
+ break;
+ }
+
+ try {
+ Heavy.glocation(context, replyTo, OnOff.valueOf(args[0].toUpperCase(Locale.ENGLISH)) == OnOff.ON);
+ } catch(IllegalArgumentException e) {
+ sendMessage(context, replyTo, could_not_parse_argument_allowed_values_are, join(", ", toNonNull(OnOff.values())));
+ }
+ break;
+
+ case REBOOT:
+ if(args.length>1){
+ Heavy.help(context, replyTo, toNonNull(Command.REBOOT));
+ break;
+ }
+
+ Heavy.reboot(context, replyTo, args.length==0?null:args[0]);
+ break;
+
+ case SHUTDOWN:
+ //TODO: implement command
+ break;
+
+ case NOTIFY:
+ if(args.length!=1 && args.length!=3){
+ Heavy.help(context, replyTo, toNonNull(Command.NOTIFY));
+ }
+
+ final int id;
+ try{
+ id=Integer.parseInt(args[0]);
+ } catch (NumberFormatException e){
+ sendMessage(context, replyTo, R.string.could_not_parse_id);
+ break;
+ }
+
+ if(args.length==1)
+ Heavy.notify(context, replyTo, id);
+ else
+ Heavy.notify(context, replyTo, id, toNonNull(args[1]), toNonNull(args[2]));
+ break;
+ }
+
+ }
+
+ /**
+ * Executes a given command
+ *
+ * @param context Context instance
+ * @param cmd Command to execute
+ * @param args arguments for the command
+ * @param replyTo Address to send replies to
+ */
+ public static void processCommand(final Context context, final String cmd,final String[] args,final Address replyTo){
+ final @NonNull Command command;
+ try{
+ command=toNonNull(Command.valueOf(cmd.toUpperCase(Locale.ENGLISH)));
+ } catch (IllegalArgumentException e){
+ sendMessage(context, replyTo, unknown_command, cmd.toUpperCase(Locale.ENGLISH), e.getMessage());
+ return;
+ }
+
+ try{
+ processCommand(context, command,args,replyTo);
+ } catch(Exception e){
+ sendMessage(context, replyTo, error_while_processing_command, e.getClass().getName(), e.getMessage());
+ Log.w(Utils.class.getName(), "Error while processing command", e);
+ }
+ }
+}
--- /dev/null
+
+/**
+ * The main package of FonBot
+ */
+@NonNullByDefault
+package ro.ieval.fonbot;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/*
+ * Copyright © 2013 Marius Gavrilescu
+ *
+ * This file is part of FonBot.
+ *
+ * FonBot is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FonBot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FonBot. If not, see <http://www.gnu.org/licenses/>.
+ */
\ No newline at end of file