Grundaufbau der App
authorpk910 <philipp@pk910.de>
Wed, 27 Jan 2016 10:29:18 +0000 (11:29 +0100)
committerpk910 <philipp@pk910.de>
Wed, 27 Jan 2016 10:29:18 +0000 (11:29 +0100)
172 files changed:
.gitignore [new file with mode: 0644]
.idea/.name [new file with mode: 0644]
.idea/compiler.xml [new file with mode: 0644]
.idea/copyright/profiles_settings.xml [new file with mode: 0644]
.idea/encodings.xml [new file with mode: 0644]
.idea/gradle.xml [new file with mode: 0644]
.idea/misc.xml [new file with mode: 0644]
.idea/modules.xml [new file with mode: 0644]
.idea/runConfigurations.xml [new file with mode: 0644]
.idea/vcs.xml [new file with mode: 0644]
app/.gitignore [new file with mode: 0644]
app/build.gradle [new file with mode: 0644]
app/proguard-rules.pro [new file with mode: 0644]
app/src/androidTest/java/de/dhbwloe/campusapp/ApplicationTest.java [new file with mode: 0644]
app/src/main/AndroidManifest.xml [new file with mode: 0644]
app/src/main/ic_close_white-web.png [new file with mode: 0644]
app/src/main/ic_search-web.png [new file with mode: 0644]
app/src/main/java/com/codebutler/farebot/Utils.java [new file with mode: 0644]
app/src/main/java/com/codebutler/farebot/card/desfire/DesfireApplication.java [new file with mode: 0644]
app/src/main/java/com/codebutler/farebot/card/desfire/DesfireException.java [new file with mode: 0644]
app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFile.java [new file with mode: 0644]
app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFileSettings.java [new file with mode: 0644]
app/src/main/java/com/codebutler/farebot/card/desfire/DesfireManufacturingData.java [new file with mode: 0644]
app/src/main/java/com/codebutler/farebot/card/desfire/DesfireProtocol.java [new file with mode: 0644]
app/src/main/java/com/codebutler/farebot/card/desfire/DesfireRecord.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/AppCompatPreferenceActivity.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/CampusApp.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/CampusAppContext.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/CampusAppFragment.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/NavigationManager.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/SettingsActivity.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/Tools.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/database/DatabaseManager.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/database/MensaplanDatabaseHelper.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/database/NewsDatabaseHelper.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/database/NfcCardData.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/database/VorlesungsplanDatabaseHelper.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearch.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchDhbw.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchInternal.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchListAdapter.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchListItem.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchProvider.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchStuv.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/Dashboard.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/FirstRun.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/Impressum.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/Mensa.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/MensaCard.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/MensaTagesplan.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/MensaTagesplanListAdapter.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/MensaWochenplan.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/News.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/PopupFragment.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/SplashScreen.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/Vorlesungsplan.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/WebBrowser.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/fragments/WifiSettings.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/mensaplan/MensaTagesplan.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/mensaplan/MensaplanManager.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/mensaplan/MensaplanManagerInterface.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/network/IscRequestHelper.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/network/XmlEntry.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/network/XmlRequestHelper.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/news/NewsItem.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/news/NewsManager.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/news/NewsManagerInterface.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/nfcreader/NfcCardInterface.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/nfcreader/NfcCardListener.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/ICardReader.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/IntercardReader.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/MagnaCartaReader.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/NfcCardData.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/Readers.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/search/DhbwSearchHelper.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/search/SearchHelper.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/search/SearchIndices.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/search/SearchResultListener.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/search/SearchTarget.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/search/StuvSearchHelper.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/vorlesungen/CourseEvent.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/vorlesungen/CourseGroup.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/vorlesungen/VorlesungsplanManager.java [new file with mode: 0644]
app/src/main/java/de/dhbwloe/campusapp/vorlesungen/VorlesungsplanManagerInterface.java [new file with mode: 0644]
app/src/main/res/drawable-hdpi/ic_info_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-mdpi/ic_info_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-v21/ic_info_black_24dp.xml [new file with mode: 0644]
app/src/main/res/drawable-v21/ic_menu_camera.xml [new file with mode: 0644]
app/src/main/res/drawable-v21/ic_menu_gallery.xml [new file with mode: 0644]
app/src/main/res/drawable-v21/ic_menu_manage.xml [new file with mode: 0644]
app/src/main/res/drawable-v21/ic_menu_send.xml [new file with mode: 0644]
app/src/main/res/drawable-v21/ic_menu_share.xml [new file with mode: 0644]
app/src/main/res/drawable-v21/ic_menu_slideshow.xml [new file with mode: 0644]
app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml [new file with mode: 0644]
app/src/main/res/drawable-v21/ic_sync_black_24dp.xml [new file with mode: 0644]
app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png [new file with mode: 0644]
app/src/main/res/drawable/appsearch_resulttype_external.xml [new file with mode: 0644]
app/src/main/res/drawable/appsearch_resulttype_inapp.xml [new file with mode: 0644]
app/src/main/res/drawable/dhbw_campus.jpg [new file with mode: 0644]
app/src/main/res/drawable/dhbw_campus_hd.jpg [new file with mode: 0644]
app/src/main/res/drawable/dhbw_logo.png [new file with mode: 0644]
app/src/main/res/drawable/side_nav_bar.xml [new file with mode: 0644]
app/src/main/res/layout/activity_campus_app.xml [new file with mode: 0644]
app/src/main/res/layout/app_bar_campus_app.xml [new file with mode: 0644]
app/src/main/res/layout/content_campus_app.xml [new file with mode: 0644]
app/src/main/res/layout/content_popup.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_appsearch.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_appsearch_list.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_appsearch_listitem.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_dashboard.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_first_run.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_impressum.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_mensa.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_mensa_card.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_mensa_tagesplan.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_mensa_tagesplan_listitem.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_mensa_wochenplan.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_news.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_splashscreen.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_vorlesungsplan.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_web_browser.xml [new file with mode: 0644]
app/src/main/res/layout/fragment_wifi_settings.xml [new file with mode: 0644]
app/src/main/res/layout/nav_header_campus_app.xml [new file with mode: 0644]
app/src/main/res/menu/activity_campus_app_drawer.xml [new file with mode: 0644]
app/src/main/res/menu/campus_app.xml [new file with mode: 0644]
app/src/main/res/mipmap-hdpi/ic_close_white.png [new file with mode: 0644]
app/src/main/res/mipmap-hdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/mipmap-hdpi/ic_search.png [new file with mode: 0644]
app/src/main/res/mipmap-mdpi/ic_close_white.png [new file with mode: 0644]
app/src/main/res/mipmap-mdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/mipmap-mdpi/ic_search.png [new file with mode: 0644]
app/src/main/res/mipmap-xhdpi/ic_close_white.png [new file with mode: 0644]
app/src/main/res/mipmap-xhdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/mipmap-xhdpi/ic_search.png [new file with mode: 0644]
app/src/main/res/mipmap-xxhdpi/ic_close_white.png [new file with mode: 0644]
app/src/main/res/mipmap-xxhdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/mipmap-xxhdpi/ic_search.png [new file with mode: 0644]
app/src/main/res/mipmap-xxxhdpi/ic_close_white.png [new file with mode: 0644]
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png [new file with mode: 0644]
app/src/main/res/mipmap-xxxhdpi/ic_search.png [new file with mode: 0644]
app/src/main/res/values-v21/styles.xml [new file with mode: 0644]
app/src/main/res/values-w820dp/dimens.xml [new file with mode: 0644]
app/src/main/res/values/colors.xml [new file with mode: 0644]
app/src/main/res/values/customids.xml [new file with mode: 0644]
app/src/main/res/values/dimens.xml [new file with mode: 0644]
app/src/main/res/values/drawables.xml [new file with mode: 0644]
app/src/main/res/values/strings.xml [new file with mode: 0644]
app/src/main/res/values/styles.xml [new file with mode: 0644]
app/src/main/res/xml/pref_data_sync.xml [new file with mode: 0644]
app/src/main/res/xml/pref_general.xml [new file with mode: 0644]
app/src/main/res/xml/pref_headers.xml [new file with mode: 0644]
app/src/main/res/xml/pref_notification.xml [new file with mode: 0644]
app/src/test/java/de/dhbwloe/campusapp/ExampleUnitTest.java [new file with mode: 0644]
build.gradle [new file with mode: 0644]
gradle.properties [new file with mode: 0644]
gradle/wrapper/gradle-wrapper.jar [new file with mode: 0644]
gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
gradlew [new file with mode: 0644]
gradlew.bat [new file with mode: 0644]
settings.gradle [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c6cbe56
--- /dev/null
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/.idea/.name b/.idea/.name
new file mode 100644 (file)
index 0000000..fc882fd
--- /dev/null
@@ -0,0 +1 @@
+DHBW Campus App
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644 (file)
index 0000000..9a8b7e5
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac" />
+    <resourceExtensions />
+    <wildcardResourcePatterns>
+      <entry name="!?*.java" />
+      <entry name="!?*.form" />
+      <entry name="!?*.class" />
+      <entry name="!?*.groovy" />
+      <entry name="!?*.scala" />
+      <entry name="!?*.flex" />
+      <entry name="!?*.kt" />
+      <entry name="!?*.clj" />
+    </wildcardResourcePatterns>
+    <annotationProcessing>
+      <profile default="true" name="Default" enabled="false">
+        <processorPath useClasspath="true" />
+      </profile>
+    </annotationProcessing>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644 (file)
index 0000000..e7bedf3
--- /dev/null
@@ -0,0 +1,3 @@
+<component name="CopyrightManager">
+  <settings default="" />
+</component>
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644 (file)
index 0000000..97626ba
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644 (file)
index 0000000..d390b65
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="distributionType" value="LOCAL" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="gradleHome" value="C:\Program Files\Android\Android Studio\gradle\gradle-2.8" />
+        <option name="gradleJvm" value="1.7" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644 (file)
index 0000000..6a1e020
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="EntryPointsManager">
+    <entry_points version="2.0" />
+  </component>
+  <component name="NullableNotNullManager">
+    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
+    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
+    <option name="myNullables">
+      <value>
+        <list size="4">
+          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
+          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
+          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
+          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
+        </list>
+      </value>
+    </option>
+    <option name="myNotNulls">
+      <value>
+        <list size="4">
+          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
+          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
+          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
+          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
+        </list>
+      </value>
+    </option>
+  </component>
+  <component name="ProjectLevelVcsManager" settingsEditedManually="false">
+    <OptionsSetting value="true" id="Add" />
+    <OptionsSetting value="true" id="Remove" />
+    <OptionsSetting value="true" id="Checkout" />
+    <OptionsSetting value="true" id="Update" />
+    <OptionsSetting value="true" id="Status" />
+    <OptionsSetting value="true" id="Edit" />
+    <ConfirmationsSetting value="0" id="Add" />
+    <ConfirmationsSetting value="0" id="Remove" />
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644 (file)
index 0000000..a1be715
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/DHBWCampusApp.iml" filepath="$PROJECT_DIR$/DHBWCampusApp.iml" />
+      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644 (file)
index 0000000..7f68460
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RunConfigurationProducerService">
+    <option name="ignoredProducers">
+      <set>
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
+      </set>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644 (file)
index 0000000..94a25f7
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644 (file)
index 0000000..796b96d
--- /dev/null
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644 (file)
index 0000000..6414d6d
--- /dev/null
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "23.0.2"
+
+    defaultConfig {
+        applicationId "de.dhbwloe.campusapp"
+        minSdkVersion 16
+        targetSdkVersion 23
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    packagingOptions {
+        exclude 'META-INF/DEPENDENCIES.txt'
+        exclude 'META-INF/LICENSE.txt'
+        exclude 'META-INF/NOTICE.txt'
+        exclude 'META-INF/NOTICE'
+        exclude 'META-INF/LICENSE'
+        exclude 'META-INF/DEPENDENCIES'
+        exclude 'META-INF/notice.txt'
+        exclude 'META-INF/license.txt'
+        exclude 'META-INF/dependencies.txt'
+        exclude 'META-INF/LGPL2.1'
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    testCompile 'junit:junit:4.12'
+    compile 'org.mnode.ical4j:ical4j:1.0.5'
+    compile 'backport-util-concurrent:backport-util-concurrent:3.1'
+    compile 'commons-codec:commons-codec:1.8'
+    compile 'commons-lang:commons-lang:2.6'
+    compile 'com.loopj.android:android-async-http:1.4.9'
+    compile 'org.jsoup:jsoup:1.8.3'
+    compile 'org.apache.commons:commons-lang3:3.1'
+    compile 'com.android.support:appcompat-v7:23.1.1'
+    compile 'com.android.support:design:23.1.1'
+    compile 'com.android.support:support-v4:23.1.1'
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644 (file)
index 0000000..2b76c9d
--- /dev/null
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:\android-sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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 *;
+#}
diff --git a/app/src/androidTest/java/de/dhbwloe/campusapp/ApplicationTest.java b/app/src/androidTest/java/de/dhbwloe/campusapp/ApplicationTest.java
new file mode 100644 (file)
index 0000000..ce43036
--- /dev/null
@@ -0,0 +1,13 @@
+package de.dhbwloe.campusapp;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+    public ApplicationTest() {
+        super(Application.class);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..89c3c04
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.dhbwloe.campusapp">
+
+    <uses-permission android:name="android.permission.NFC" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+
+    <uses-feature
+        android:name="android.hardware.nfc"
+        android:required="false" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".CampusApp"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".SettingsActivity"
+            android:label="@string/title_activity_settings"
+            android:parentActivityName=".CampusApp">
+            <meta-data
+                android:name="android.support.PARENT_ACTIVITY"
+                android:value="de.dhbwloe.campusapp.CampusApp" />
+        </activity>
+    </application>
+
+</manifest>
diff --git a/app/src/main/ic_close_white-web.png b/app/src/main/ic_close_white-web.png
new file mode 100644 (file)
index 0000000..5b55c69
Binary files /dev/null and b/app/src/main/ic_close_white-web.png differ
diff --git a/app/src/main/ic_search-web.png b/app/src/main/ic_search-web.png
new file mode 100644 (file)
index 0000000..a31172c
Binary files /dev/null and b/app/src/main/ic_search-web.png differ
diff --git a/app/src/main/java/com/codebutler/farebot/Utils.java b/app/src/main/java/com/codebutler/farebot/Utils.java
new file mode 100644 (file)
index 0000000..c77b93c
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * Utils.java
+ *
+ * Copyright (C) 2011 Eric Butler
+ *
+ * Authors:
+ * Eric Butler <eric@codebutler.com>
+ *
+ * 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/>.
+ */
+
+package com.codebutler.farebot;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.codebutler.farebot.card.desfire.DesfireException;
+import com.codebutler.farebot.card.desfire.DesfireFileSettings;
+import com.codebutler.farebot.card.desfire.DesfireProtocol;
+
+import org.w3c.dom.Node;
+
+import java.io.StringWriter;
+import java.util.List;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+public class Utils {
+
+       private static final String TAG = Utils.class.getName();
+
+    public static void showError (final Activity activity, Exception ex) {
+        Log.e(activity.getClass().getName(), ex.getMessage(), ex);
+        new AlertDialog.Builder(activity)
+            .setMessage(Utils.getErrorMessage(ex))
+            .show();
+    }
+
+    public static void showErrorAndFinish (final Activity activity, Exception ex) {
+        try {
+            Log.e(activity.getClass().getName(), Utils.getErrorMessage(ex));
+            ex.printStackTrace();
+
+            new AlertDialog.Builder(activity)
+                .setMessage(Utils.getErrorMessage(ex))
+                .setCancelable(false)
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface arg0, int arg1) {
+                        activity.finish();
+                    }
+                })
+                .show();
+        } catch (WindowManager.BadTokenException unused) {
+            /* Ignore... happens if the activity was destroyed */
+        }
+    }
+
+    public static String getHexString (byte[] b) throws Exception {
+        String result = "";
+        for (int i=0; i < b.length; i++) {
+            result += Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
+        }
+        return result;
+    }
+
+    public static String getHexString (byte[] b, String defaultResult) {
+        try {
+            return getHexString(b);
+        } catch (Exception ex) {
+            return defaultResult;
+        }
+    }
+
+    public static byte[] hexStringToByteArray (String s) {
+        if ((s.length() % 2) != 0) {
+            throw new IllegalArgumentException("Bad input string: " + s);
+        }
+        
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+                                 + Character.digit(s.charAt(i+1), 16));
+        }
+        return data;
+    }
+
+    /*
+    public static byte[] intToByteArray(int value) {
+        return new byte[] {
+                (byte)(value >>> 24),
+                (byte)(value >>> 16),
+                (byte)(value >>> 8),
+                (byte)value};
+    }
+    */
+    
+    public static int byteArrayToInt(byte[] b) {
+        return byteArrayToInt(b, 0);
+    }
+    
+    public static int byteArrayToInt(byte[] b, int offset) {
+        return byteArrayToInt(b, offset, b.length);
+    }
+    
+    public static int byteArrayToInt(byte[] b, int offset, int length) {
+        return (int) byteArrayToLong(b, offset, length);
+    }
+
+    public static long byteArrayToLong(byte[] b, int offset, int length) {
+        if (b.length < length)
+            throw new IllegalArgumentException("length must be less than or equal to b.length");
+
+        long value = 0;
+        for (int i = 0; i < length; i++) {
+            int shift = (length - 1 - i) * 8;
+            value += (b[i + offset] & 0x000000FF) << shift;
+        }
+        return value;
+    }
+
+    public static byte[] byteArraySlice(byte[] b, int offset, int length) {
+        byte[] ret = new byte[length];
+        for (int i = 0; i < length; i++)
+            ret[i] = b[offset+i];
+        return ret;
+    }
+
+    public static String xmlNodeToString (Node node) throws Exception {
+        // The amount of code required to do simple things in Java is incredible.
+        Source source = new DOMSource(node);
+        StringWriter stringWriter = new StringWriter();
+        Result result = new StreamResult(stringWriter);
+        TransformerFactory factory = TransformerFactory.newInstance();
+        Transformer transformer = factory.newTransformer();
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+        transformer.setURIResolver(null);
+        transformer.transform(source, result);
+        return stringWriter.getBuffer().toString();
+    }
+
+    public static String getErrorMessage (Throwable ex) {
+        String errorMessage = ex.getLocalizedMessage();
+        if (errorMessage == null)
+            errorMessage = ex.getMessage();
+        if (errorMessage == null)
+            errorMessage = ex.toString();
+
+        if (ex.getCause() != null) {
+            String causeMessage = ex.getCause().getLocalizedMessage();
+            if (causeMessage == null)
+                causeMessage = ex.getCause().getMessage();
+            if (causeMessage == null)
+                causeMessage = ex.getCause().toString();
+
+            if (causeMessage != null)
+                errorMessage += ": " + causeMessage;
+        }
+
+        return errorMessage;
+    }
+
+
+    public static <T> T findInList(List<T> list, Matcher<T> matcher) {
+        for (T item : list) {
+            if (matcher.matches(item)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    public static interface Matcher<T> {
+        public boolean matches(T t);
+    }
+
+    public static int convertBCDtoInteger(byte data) {
+        return (((data & (char)0xF0) >> 4) * 10) + ((data & (char)0x0F));
+    }
+
+    public static int getBitsFromInteger(int buffer, int iStartBit, int iLength) {
+        return (buffer >> (iStartBit)) & ((char)0xFF >> (8 - iLength));
+    }
+
+    /* Based on function from mfocGUI by 'Huuf' (http://www.huuf.info/OV/) */
+    public static int getBitsFromBuffer(byte[] buffer, int iStartBit, int iLength) {
+        int iEndBit = iStartBit + iLength - 1;
+        int iSByte = iStartBit / 8;
+        int iSBit = iStartBit % 8;
+        int iEByte = iEndBit / 8;
+        int iEBit = iEndBit % 8;
+
+        if (iSByte == iEByte) {
+            return (int)(((char)buffer[iEByte] >> (7 - iEBit)) & ((char)0xFF >> (8 - iLength)));
+        } else {
+            int uRet = (((char)buffer[iSByte] & (char)((char)0xFF >> iSBit)) << (((iEByte - iSByte - 1) * 8) + (iEBit + 1)));
+
+            for (int i = iSByte + 1; i < iEByte; i++) {
+                uRet |= (((char)buffer[i] & (char)0xFF) << (((iEByte - i - 1) * 8) + (iEBit + 1)));
+            }
+
+            uRet |= (((char)buffer[iEByte] & (char)0xFF)) >> (7 - iEBit);
+
+            return uRet;
+        }
+    }
+
+
+       public static DesfireFileSettings selectAppFile(DesfireProtocol tag, int appID, int fileID) {
+               try {
+                       tag.selectApp(appID);
+               } catch (DesfireException e) {
+                       Log.w(TAG,"App not found");
+                       return null;
+               }
+               try {
+                       return tag.getFileSettings(fileID);
+               } catch (DesfireException e) {
+                       Log.w(TAG,"File not found");
+                       return null;
+               }
+       }
+
+       public static boolean arrayContains(int[] arr, int item) {
+               for (int i: arr)
+                       if (i==item)
+                               return true;
+               return false;
+       }
+
+       public static boolean containsAppFile(DesfireProtocol tag, int appID, int fileID) {
+               try {
+                       tag.selectApp(appID);
+               } catch (DesfireException e) {
+                       Log.w(TAG,"App not found");
+                       Log.w(TAG, e);
+                       return false;
+               }
+               try {
+                       return arrayContains(tag.getFileList(),fileID);
+               } catch (DesfireException e) {
+                       Log.w(TAG,"File not found");
+                       Log.w(TAG, e);
+                       return false;
+               }
+       }
+}
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireApplication.java b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireApplication.java
new file mode 100644 (file)
index 0000000..bda03a6
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * DesfireApplication.java
+ *
+ * Copyright (C) 2011 Eric Butler
+ *
+ * Authors:
+ * Eric Butler <eric@codebutler.com>
+ *
+ * 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/>.
+ */
+
+package com.codebutler.farebot.card.desfire;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class DesfireApplication implements Parcelable {
+    private int           mId;
+    private DesfireFile[] mFiles;
+
+    public DesfireApplication (int id, DesfireFile[] files) {
+        mId    = id;
+        mFiles = files;
+    }
+
+    public int getId () {
+        return mId;
+    }
+
+    public DesfireFile[] getFiles () {
+        return mFiles;
+    }
+
+    public DesfireFile getFile (int fileId) {
+        for (DesfireFile file : mFiles) {
+            if (file.getId() == fileId)
+                return file;
+        }
+        return null;
+    }
+
+    public static final Parcelable.Creator<DesfireApplication> CREATOR = new Parcelable.Creator<DesfireApplication>() {
+        public DesfireApplication createFromParcel(Parcel source) {
+            int id = source.readInt();
+
+            DesfireFile[] files = new DesfireFile[source.readInt()];
+            source.readTypedArray(files, DesfireFile.CREATOR);
+
+            return new DesfireApplication(id, files);
+        }
+
+        public DesfireApplication[] newArray (int size) {
+            return new DesfireApplication[size];
+        }
+    };
+
+    public void writeToParcel (Parcel parcel, int flags) {
+        parcel.writeInt(mId);
+        parcel.writeInt(mFiles.length);
+        parcel.writeTypedArray(mFiles, flags);
+    }
+
+    public int describeContents () {
+        return 0;
+    }    
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireException.java b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireException.java
new file mode 100644 (file)
index 0000000..b737d38
--- /dev/null
@@ -0,0 +1,13 @@
+package com.codebutler.farebot.card.desfire;
+
+/**
+ * Created by Jakob Wenzel on 16.11.13.
+ */
+public class DesfireException extends Exception {
+       public DesfireException(String message) {
+               super(message);
+       }
+       public DesfireException(Throwable cause) {
+               super(cause);
+       }
+}
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFile.java b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFile.java
new file mode 100644 (file)
index 0000000..80ae5fc
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * DesfireFile.java
+ *
+ * Copyright (C) 2011 Eric Butler
+ *
+ * Authors:
+ * Eric Butler <eric@codebutler.com>
+ *
+ * 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/>.
+ */
+
+package com.codebutler.farebot.card.desfire;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.codebutler.farebot.card.desfire.DesfireFileSettings.RecordDesfireFileSettings;
+
+public class DesfireFile implements Parcelable {
+    private int                 mId;
+    private DesfireFileSettings mSettings;
+    private byte[]              mData;
+
+    public static DesfireFile create (int fileId, DesfireFileSettings fileSettings, byte[] fileData) {
+        if (fileSettings instanceof RecordDesfireFileSettings)
+            return new RecordDesfireFile(fileId, fileSettings, fileData);
+        else
+            return new DesfireFile(fileId, fileSettings, fileData);
+    }
+
+    private DesfireFile (int fileId, DesfireFileSettings fileSettings, byte[] fileData) {
+        mId       = fileId;
+        mSettings = fileSettings;
+        mData     = fileData;
+    }
+
+    public DesfireFileSettings getFileSettings () {
+        return mSettings;
+    }
+
+    public int getId () {
+        return mId;
+    }
+
+    public byte[] getData () {
+        return mData;
+    }
+
+    public static final Parcelable.Creator<DesfireFile> CREATOR = new Parcelable.Creator<DesfireFile>() {
+        public DesfireFile createFromParcel(Parcel source) {
+            int fileId = source.readInt();
+
+            boolean isError = (source.readInt() == 1);
+
+            if (!isError) {
+                DesfireFileSettings fileSettings = (DesfireFileSettings) source.readParcelable(DesfireFileSettings.class.getClassLoader());
+                int    dataLength = source.readInt();
+                byte[] fileData   = new byte[dataLength];
+                source.readByteArray(fileData);
+
+                return DesfireFile.create(fileId, fileSettings, fileData);
+            } else {
+                return new InvalidDesfireFile(fileId, source.readString());
+            }
+        }
+
+        public DesfireFile[] newArray (int size) {
+            return new DesfireFile[size];
+        }
+    };
+
+    public void writeToParcel (Parcel parcel, int flags) {
+        parcel.writeInt(mId);
+        if (this instanceof InvalidDesfireFile) {
+            parcel.writeInt(1);
+            parcel.writeString(((InvalidDesfireFile)this).getErrorMessage());
+        } else {
+            parcel.writeInt(0);
+            parcel.writeParcelable(mSettings, 0);
+            parcel.writeInt(mData.length);
+            parcel.writeByteArray(mData);
+        }
+    }
+
+    public int describeContents () {
+        return 0;
+    }
+
+    public static class RecordDesfireFile extends DesfireFile {
+        private DesfireRecord[] mRecords;
+
+        private RecordDesfireFile (int fileId, DesfireFileSettings fileSettings, byte[] fileData) {
+            super(fileId, fileSettings, fileData);
+
+            RecordDesfireFileSettings settings = (RecordDesfireFileSettings) fileSettings;
+
+            DesfireRecord[] records = new DesfireRecord[settings.curRecords];
+            for (int i = 0; i < settings.curRecords; i++) {
+                int offset = settings.recordSize * i;
+                records[i] = new DesfireRecord(ArrayUtils.subarray(getData(), offset, offset + settings.recordSize));
+            }
+            mRecords = records;
+        }
+
+        public DesfireRecord[] getRecords () {
+            return mRecords;
+        }
+    }
+
+    public static class InvalidDesfireFile extends DesfireFile {
+        private String mErrorMessage;
+
+        public InvalidDesfireFile (int fileId, String errorMessage) {
+            super(fileId, null, new byte[0]);
+            mErrorMessage = errorMessage;
+        }
+
+        public String getErrorMessage () {
+            return mErrorMessage;
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFileSettings.java b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireFileSettings.java
new file mode 100644 (file)
index 0000000..17220b7
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * DesfireFileSettings.java
+ *
+ * Copyright (C) 2011 Eric Butler
+ *
+ * Authors:
+ * Eric Butler <eric@codebutler.com>
+ *
+ * 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/>.
+ */
+
+package com.codebutler.farebot.card.desfire;
+
+import java.io.ByteArrayInputStream;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.codebutler.farebot.Utils;
+
+public abstract class DesfireFileSettings implements Parcelable {
+    public final byte   fileType;
+    public final byte   commSetting;
+    public final byte[] accessRights;
+
+    /* DesfireFile Types */
+    static final byte STANDARD_DATA_FILE = (byte) 0x00;
+    static final byte BACKUP_DATA_FILE   = (byte) 0x01;
+    static final byte VALUE_FILE         = (byte) 0x02;
+    static final byte LINEAR_RECORD_FILE = (byte) 0x03;
+    static final byte CYCLIC_RECORD_FILE = (byte) 0x04;
+    
+    public static DesfireFileSettings Create (byte[] data) throws DesfireException {
+        byte fileType = (byte) data[0];
+
+        ByteArrayInputStream stream = new ByteArrayInputStream(data);
+
+        if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE)
+            return new StandardDesfireFileSettings(stream);
+        else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE)
+            return new RecordDesfireFileSettings(stream);
+        else if (fileType == VALUE_FILE)
+            return new ValueDesfireFileSettings(stream);
+        else
+            throw new DesfireException("Unknown file type: " + Integer.toHexString(fileType));
+    }
+
+    private DesfireFileSettings (ByteArrayInputStream stream) {
+        fileType    = (byte) stream.read();
+        commSetting = (byte) stream.read();
+
+        accessRights = new byte[2];
+        stream.read(accessRights, 0, accessRights.length);
+    }
+
+    private DesfireFileSettings (byte fileType, byte commSetting, byte[] accessRights) {
+        this.fileType     = fileType;
+        this.commSetting  = commSetting;
+        this.accessRights = accessRights;
+    }
+
+    public String getFileTypeName () {
+        switch (fileType) {
+            case STANDARD_DATA_FILE:
+                return "Standard";
+            case BACKUP_DATA_FILE:
+                return "Backup";
+            case VALUE_FILE:
+                return "Value";
+            case LINEAR_RECORD_FILE:
+                return "Linear Record";
+            case CYCLIC_RECORD_FILE:
+                return "Cyclic Record";
+            default:
+                return "Unknown";
+        }
+    }
+
+    public static final Parcelable.Creator<DesfireFileSettings> CREATOR = new Parcelable.Creator<DesfireFileSettings>() {
+        public DesfireFileSettings createFromParcel(Parcel source) {
+            byte fileType       = source.readByte();
+            byte commSetting    = source.readByte();
+            byte[] accessRights = new byte[source.readInt()];
+            source.readByteArray(accessRights);
+
+            if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE) {
+                int fileSize = source.readInt();
+                return new StandardDesfireFileSettings(fileType, commSetting, accessRights, fileSize);
+            } else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE) {
+                int recordSize = source.readInt();
+                int maxRecords = source.readInt();
+                int curRecords = source.readInt();
+                return new RecordDesfireFileSettings(fileType, commSetting, accessRights, recordSize, maxRecords, curRecords);
+            } else {
+                return new UnsupportedDesfireFileSettings(fileType);
+            }
+        }
+
+        public DesfireFileSettings[] newArray(int size) {
+            return new DesfireFileSettings[size];
+        }
+    };
+
+    public void writeToParcel (Parcel parcel, int flags) {
+        parcel.writeByte(fileType);
+        parcel.writeByte(commSetting);
+        parcel.writeInt(accessRights.length);
+        parcel.writeByteArray(accessRights);
+    }
+
+    public int describeContents () {
+        return 0;
+    }
+
+    public static class StandardDesfireFileSettings extends DesfireFileSettings {
+        public final int fileSize;
+
+        private StandardDesfireFileSettings (ByteArrayInputStream stream) {
+            super(stream);
+            byte[] buf = new byte[3];
+            stream.read(buf, 0, buf.length);
+            ArrayUtils.reverse(buf);
+            fileSize = Utils.byteArrayToInt(buf);
+        }
+
+        StandardDesfireFileSettings (byte fileType, byte commSetting, byte[] accessRights, int fileSize) {
+            super(fileType, commSetting, accessRights);
+            this.fileSize = fileSize;
+        }
+
+        @Override
+        public void writeToParcel (Parcel parcel, int flags) {
+            super.writeToParcel(parcel, flags);
+            parcel.writeInt(fileSize);
+        }
+    }
+
+    public static class RecordDesfireFileSettings extends DesfireFileSettings {
+        public final int recordSize;
+        public final int maxRecords;
+        public final int curRecords;
+
+        public RecordDesfireFileSettings(ByteArrayInputStream stream) {
+            super(stream);
+
+            byte[] buf = new byte[3];
+            stream.read(buf, 0, buf.length);
+            ArrayUtils.reverse(buf);
+            recordSize = Utils.byteArrayToInt(buf);
+
+            buf = new byte[3];
+            stream.read(buf, 0, buf.length);
+            ArrayUtils.reverse(buf);
+            maxRecords = Utils.byteArrayToInt(buf);
+
+            buf = new byte[3];
+            stream.read(buf, 0, buf.length);
+            ArrayUtils.reverse(buf);
+            curRecords = Utils.byteArrayToInt(buf);
+        }
+
+        RecordDesfireFileSettings (byte fileType, byte commSetting, byte[] accessRights, int recordSize, int maxRecords, int curRecords) {
+            super(fileType, commSetting, accessRights);
+            this.recordSize = recordSize;
+            this.maxRecords = maxRecords;
+            this.curRecords = curRecords;
+        }
+
+        @Override
+        public void writeToParcel (Parcel parcel, int flags) {
+            super.writeToParcel(parcel, flags);
+            parcel.writeInt(recordSize);
+            parcel.writeInt(maxRecords);
+            parcel.writeInt(curRecords);
+        }
+    }
+
+
+
+
+    public static class ValueDesfireFileSettings extends DesfireFileSettings {
+        public final int lowerLimit;
+        public final int upperLimit;
+        public final int value;
+        public final byte limitedCreditEnabled;
+
+        public ValueDesfireFileSettings(ByteArrayInputStream stream) {
+            super(stream);
+
+            byte[] buf = new byte[4];
+            stream.read(buf, 0, buf.length);
+            ArrayUtils.reverse(buf);
+            lowerLimit = Utils.byteArrayToInt(buf);
+
+            buf = new byte[4];
+            stream.read(buf, 0, buf.length);
+            ArrayUtils.reverse(buf);
+            upperLimit = Utils.byteArrayToInt(buf);
+
+            buf = new byte[4];
+            stream.read(buf, 0, buf.length);
+            ArrayUtils.reverse(buf);
+            value = Utils.byteArrayToInt(buf);
+            
+
+            buf = new byte[1];
+            stream.read(buf, 0, buf.length);
+            limitedCreditEnabled = buf[0];
+            
+            //http://www.skyetek.com/docs/m2/desfire.pdf
+            //http://neteril.org/files/M075031_desfire.pdf
+        }
+
+        @Override
+        public void writeToParcel (Parcel parcel, int flags) {
+            super.writeToParcel(parcel, flags);
+            parcel.writeInt(lowerLimit);
+            parcel.writeInt(upperLimit);
+            parcel.writeInt(value);
+            parcel.writeByte(limitedCreditEnabled);
+        }
+    }
+    public static class UnsupportedDesfireFileSettings extends DesfireFileSettings {
+        public UnsupportedDesfireFileSettings(byte fileType) {
+            super(fileType, Byte.MIN_VALUE, new byte[0]);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireManufacturingData.java b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireManufacturingData.java
new file mode 100644 (file)
index 0000000..becc763
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * DesfireManufacturingData.java
+ *
+ * Copyright (C) 2011 Eric Butler
+ *
+ * Authors:
+ * Eric Butler <eric@codebutler.com>
+ *
+ * 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/>.
+ */
+
+package com.codebutler.farebot.card.desfire;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.codebutler.farebot.Utils;
+import org.w3c.dom.Element;
+
+import java.io.ByteArrayInputStream;
+
+public class DesfireManufacturingData implements Parcelable {
+    public final int hwVendorID;
+    public final int hwType;
+    public final int hwSubType;
+    public final int hwMajorVersion;
+    public final int hwMinorVersion;
+    public final int hwStorageSize;
+    public final int hwProtocol;
+
+    public final int swVendorID;
+    public final int swType;
+    public final int swSubType;
+    public final int swMajorVersion;
+    public final int swMinorVersion;
+    public final int swStorageSize;
+    public final int swProtocol;
+
+    public final int uid;
+    public final int batchNo;
+    public final int weekProd;
+    public final int yearProd;
+
+    public DesfireManufacturingData (byte[] data) {
+        ByteArrayInputStream stream = new ByteArrayInputStream(data);
+        hwVendorID     = stream.read();
+        hwType         = stream.read();
+        hwSubType      = stream.read();
+        hwMajorVersion = stream.read();
+        hwMinorVersion = stream.read();
+        hwStorageSize  = stream.read();
+        hwProtocol     = stream.read();
+
+        swVendorID     = stream.read();
+        swType         = stream.read();
+        swSubType      = stream.read();
+        swMajorVersion = stream.read();
+        swMinorVersion = stream.read();
+        swStorageSize  = stream.read();
+        swProtocol     = stream.read();
+
+        // FIXME: This has fewer digits than what's contained in EXTRA_ID, why?
+        byte[] buf = new byte[7];
+        stream.read(buf, 0, buf.length);
+        uid = Utils.byteArrayToInt(buf);
+
+        // FIXME: This is returning a negative number. Probably is unsigned.
+        buf = new byte[5];
+        stream.read(buf, 0, buf.length);
+        batchNo = Utils.byteArrayToInt(buf);
+
+        // FIXME: These numbers aren't making sense.
+        weekProd = stream.read();
+        yearProd = stream.read();
+    }
+
+    public static DesfireManufacturingData fromXml (Element element) {
+        return new DesfireManufacturingData(element);
+    }
+
+    private DesfireManufacturingData (Element element) {
+        hwVendorID     = Integer.parseInt(element.getElementsByTagName("hw-vendor-id").item(0).getTextContent());
+        hwType         = Integer.parseInt(element.getElementsByTagName("hw-type").item(0).getTextContent());
+        hwSubType      = Integer.parseInt(element.getElementsByTagName("hw-sub-type").item(0).getTextContent());
+        hwMajorVersion = Integer.parseInt(element.getElementsByTagName("hw-major-version").item(0).getTextContent());
+        hwMinorVersion = Integer.parseInt(element.getElementsByTagName("hw-minor-version").item(0).getTextContent());
+        hwStorageSize  = Integer.parseInt(element.getElementsByTagName("hw-storage-size").item(0).getTextContent());
+        hwProtocol     = Integer.parseInt(element.getElementsByTagName("hw-protocol").item(0).getTextContent());
+
+        swVendorID     = Integer.parseInt(element.getElementsByTagName("sw-vendor-id").item(0).getTextContent());
+        swType         = Integer.parseInt(element.getElementsByTagName("sw-type").item(0).getTextContent());
+        swSubType      = Integer.parseInt(element.getElementsByTagName("sw-sub-type").item(0).getTextContent());
+        swMajorVersion = Integer.parseInt(element.getElementsByTagName("sw-major-version").item(0).getTextContent());
+        swMinorVersion = Integer.parseInt(element.getElementsByTagName("sw-minor-version").item(0).getTextContent());
+        swStorageSize  = Integer.parseInt(element.getElementsByTagName("sw-storage-size").item(0).getTextContent());
+        swProtocol     = Integer.parseInt(element.getElementsByTagName("sw-protocol").item(0).getTextContent());
+
+        uid      = Integer.parseInt(element.getElementsByTagName("uid").item(0).getTextContent());
+        batchNo  = Integer.parseInt(element.getElementsByTagName("batch-no").item(0).getTextContent());
+        weekProd = Integer.parseInt(element.getElementsByTagName("week-prod").item(0).getTextContent());
+        yearProd = Integer.parseInt(element.getElementsByTagName("year-prod").item(0).getTextContent());
+    }
+
+    private DesfireManufacturingData (Parcel parcel) {
+        hwVendorID     = parcel.readInt();
+        hwType         = parcel.readInt();
+        hwSubType      = parcel.readInt();
+        hwMajorVersion = parcel.readInt();
+        hwMinorVersion = parcel.readInt();
+        hwStorageSize  = parcel.readInt();
+        hwProtocol     = parcel.readInt();
+
+        swVendorID     = parcel.readInt();
+        swType         = parcel.readInt();
+        swSubType      = parcel.readInt();
+        swMajorVersion = parcel.readInt();
+        swMinorVersion = parcel.readInt();
+        swStorageSize  = parcel.readInt();
+        swProtocol     = parcel.readInt();
+
+        uid      = parcel.readInt();
+        batchNo  = parcel.readInt();
+        weekProd = parcel.readInt();
+        yearProd = parcel.readInt();
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(hwVendorID);
+        parcel.writeInt(hwType);
+        parcel.writeInt(hwSubType);
+        parcel.writeInt(hwMajorVersion);
+        parcel.writeInt(hwMinorVersion);
+        parcel.writeInt(hwStorageSize);
+        parcel.writeInt(hwProtocol);
+
+        parcel.writeInt(swVendorID);
+        parcel.writeInt(swType);
+        parcel.writeInt(swSubType);
+        parcel.writeInt(swMajorVersion);
+        parcel.writeInt(swMinorVersion);
+        parcel.writeInt(swStorageSize);
+        parcel.writeInt(swProtocol);
+
+        parcel.writeInt(uid);
+        parcel.writeInt(batchNo);
+        parcel.writeInt(weekProd);
+        parcel.writeInt(yearProd);
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<DesfireManufacturingData> CREATOR = new Parcelable.Creator<DesfireManufacturingData>() {
+        public DesfireManufacturingData createFromParcel(Parcel source) {
+            return new DesfireManufacturingData(source);
+        }
+
+        public DesfireManufacturingData[] newArray(int size) {
+            return new DesfireManufacturingData[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireProtocol.java b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireProtocol.java
new file mode 100644 (file)
index 0000000..28404d7
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * DesfireProtocol.java
+ *
+ * Copyright (C) 2011 Eric Butler
+ *
+ * Authors:
+ * Eric Butler <eric@codebutler.com>
+ *
+ * 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/>.
+ */
+
+package com.codebutler.farebot.card.desfire;
+
+import android.nfc.tech.IsoDep;
+import com.codebutler.farebot.Utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+public class DesfireProtocol {
+    /* Commands */
+    static final byte GET_MANUFACTURING_DATA    = (byte) 0x60;
+    static final byte GET_APPLICATION_DIRECTORY = (byte) 0x6A;
+    static final byte GET_ADDITIONAL_FRAME      = (byte) 0xAF;
+    static final byte SELECT_APPLICATION        = (byte) 0x5A;
+    static final byte READ_DATA                 = (byte) 0xBD;
+    static final byte READ_RECORD               = (byte) 0xBB;
+    static final byte READ_VALUE                = (byte) 0x6C;
+    static final byte GET_FILES                 = (byte) 0x6F;
+    static final byte GET_FILE_SETTINGS         = (byte) 0xF5;
+
+    /* Status codes */
+    static final byte OPERATION_OK      = (byte) 0x00;
+    static final byte PERMISSION_DENIED = (byte) 0x9D;
+    static final byte ADDITIONAL_FRAME  = (byte) 0xAF;
+
+    private IsoDep mTagTech;
+
+    public DesfireProtocol(IsoDep tagTech) {
+        mTagTech = tagTech;
+    }
+
+    public DesfireManufacturingData getManufacturingData() throws DesfireException {
+        byte[] respBuffer = sendRequest(GET_MANUFACTURING_DATA);
+        
+        if (respBuffer.length != 28)
+            throw new DesfireException("Invalid response");
+
+        return new DesfireManufacturingData(respBuffer);
+    }
+
+    public int[] getAppList() throws DesfireException {
+        byte[] appDirBuf = sendRequest(GET_APPLICATION_DIRECTORY);
+
+        int[] appIds = new int[appDirBuf.length / 3];
+
+        for (int app = 0; app < appDirBuf.length; app += 3) {
+            byte[] appId = new byte[3];
+            System.arraycopy(appDirBuf, app, appId, 0, 3);
+
+            appIds[app / 3] = Utils.byteArrayToInt(appId);
+        }
+
+        return appIds;
+    }
+
+    public void selectApp (int appId) throws DesfireException {
+        byte[] appIdBuff = new byte[3];
+        appIdBuff[0] = (byte) ((appId & 0xFF0000) >> 16);
+        appIdBuff[1] = (byte) ((appId & 0xFF00) >> 8);
+        appIdBuff[2] = (byte) (appId & 0xFF);
+
+        sendRequest(SELECT_APPLICATION, appIdBuff);
+    }
+
+    public int[] getFileList() throws DesfireException {
+        byte[] buf = sendRequest(GET_FILES);
+        int[] fileIds = new int[buf.length];
+        for (int x = 0; x < buf.length; x++) {
+            fileIds[x] = (int)buf[x];
+        }
+        return fileIds;
+    }
+
+    public DesfireFileSettings getFileSettings (int fileNo) throws DesfireException {
+               byte[] data = new byte[0];
+               data = sendRequest(GET_FILE_SETTINGS, new byte[] { (byte) fileNo });
+               return DesfireFileSettings.Create(data);
+    }
+
+    public byte[] readFile (int fileNo) throws DesfireException {
+        return sendRequest(READ_DATA, new byte[] {
+            (byte) fileNo,
+            (byte) 0x0, (byte) 0x0, (byte) 0x0,
+            (byte) 0x0, (byte) 0x0, (byte) 0x0
+        });
+    }
+
+    public byte[] readRecord (int fileNum) throws DesfireException {
+        return sendRequest(READ_RECORD, new byte[]{
+                (byte) fileNum,
+                (byte) 0x0, (byte) 0x0, (byte) 0x0,
+                (byte) 0x0, (byte) 0x0, (byte) 0x0
+        });
+    }
+
+       public int readValue(int fileNum) throws DesfireException  {
+        byte[] buf = sendRequest(READ_VALUE, new byte[]{
+                (byte) fileNum
+        });
+        ArrayUtils.reverse(buf);
+        return Utils.byteArrayToInt(buf);
+       }
+    
+
+    private byte[] sendRequest (byte command) throws DesfireException {
+        return sendRequest(command, null);
+    }
+
+    private byte[] sendRequest (byte command, byte[] parameters) throws DesfireException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+               byte[] recvBuffer = new byte[0];
+               try {
+                       recvBuffer = mTagTech.transceive(wrapMessage(command, parameters));
+               } catch (IOException e) {
+                       throw new DesfireException(e);
+               }
+
+               while (true) {
+            if (recvBuffer[recvBuffer.length - 2] != (byte) 0x91)
+                throw new DesfireException("Invalid response");
+
+            output.write(recvBuffer, 0, recvBuffer.length - 2);
+
+            byte status = recvBuffer[recvBuffer.length - 1];
+            if (status == OPERATION_OK) {
+                break;
+            } else if (status == ADDITIONAL_FRAME) {
+                               try {
+                                       recvBuffer = mTagTech.transceive(wrapMessage(GET_ADDITIONAL_FRAME, null));
+                               } catch (IOException e) {
+                                       throw new DesfireException(e);
+                               }
+                       } else if (status == PERMISSION_DENIED) {
+                throw new DesfireException("Permission denied");
+            } else {
+                throw new DesfireException("Unknown status code: " + Integer.toHexString(status & 0xFF));
+            }
+        }
+        
+        return output.toByteArray();
+    }
+
+    private byte[] wrapMessage (byte command, byte[] parameters) throws DesfireException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        stream.write((byte) 0x90);
+        stream.write(command);
+        stream.write((byte) 0x00);
+        stream.write((byte) 0x00);
+        if (parameters != null) {
+            stream.write((byte) parameters.length);
+                       try {
+                               stream.write(parameters);
+                       } catch (IOException e) {
+                               throw new DesfireException(e);
+                       }
+               }
+        stream.write((byte) 0x00);
+
+        return stream.toByteArray();
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireRecord.java b/app/src/main/java/com/codebutler/farebot/card/desfire/DesfireRecord.java
new file mode 100644 (file)
index 0000000..2dfeda4
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * DesfireRecord.java
+ *
+ * Copyright (C) 2011 Eric Butler
+ *
+ * Authors:
+ * Eric Butler <eric@codebutler.com>
+ *
+ * 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/>.
+ */
+
+package com.codebutler.farebot.card.desfire;
+
+public class DesfireRecord {
+    private byte[] mData;
+
+    public DesfireRecord (byte[] data) {
+        mData = data;
+    }
+
+    public byte[] getData () {
+        return mData;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/dhbwloe/campusapp/AppCompatPreferenceActivity.java b/app/src/main/java/de/dhbwloe/campusapp/AppCompatPreferenceActivity.java
new file mode 100644 (file)
index 0000000..3423ee5
--- /dev/null
@@ -0,0 +1,109 @@
+package de.dhbwloe.campusapp;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+
+    private AppCompatDelegate mDelegate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getDelegate().installViewFactory();
+        getDelegate().onCreate(savedInstanceState);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        getDelegate().onPostCreate(savedInstanceState);
+    }
+
+    public ActionBar getSupportActionBar() {
+        return getDelegate().getSupportActionBar();
+    }
+
+    public void setSupportActionBar(@Nullable Toolbar toolbar) {
+        getDelegate().setSupportActionBar(toolbar);
+    }
+
+    @Override
+    public MenuInflater getMenuInflater() {
+        return getDelegate().getMenuInflater();
+    }
+
+    @Override
+    public void setContentView(@LayoutRes int layoutResID) {
+        getDelegate().setContentView(layoutResID);
+    }
+
+    @Override
+    public void setContentView(View view) {
+        getDelegate().setContentView(view);
+    }
+
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().setContentView(view, params);
+    }
+
+    @Override
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().addContentView(view, params);
+    }
+
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+        getDelegate().onPostResume();
+    }
+
+    @Override
+    protected void onTitleChanged(CharSequence title, int color) {
+        super.onTitleChanged(title, color);
+        getDelegate().setTitle(title);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        getDelegate().onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        getDelegate().onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        getDelegate().onDestroy();
+    }
+
+    public void invalidateOptionsMenu() {
+        getDelegate().invalidateOptionsMenu();
+    }
+
+    private AppCompatDelegate getDelegate() {
+        if (mDelegate == null) {
+            mDelegate = AppCompatDelegate.create(this, null);
+        }
+        return mDelegate;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/CampusApp.java b/app/src/main/java/de/dhbwloe/campusapp/CampusApp.java
new file mode 100644 (file)
index 0000000..e9200ac
--- /dev/null
@@ -0,0 +1,296 @@
+package de.dhbwloe.campusapp;
+
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.NfcAdapter;
+import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.FragmentActivity;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.support.design.widget.NavigationView;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.Date;
+
+import de.dhbwloe.campusapp.nfcreader.NfcCardListener;
+
+public class CampusApp extends FragmentActivity {
+
+    private boolean bSearchActive = false;
+    private CampusAppContext AppContext = null;
+
+    /*
+    * Dev Info:
+    *
+    * Die App besteht aus einer einzigen Activity, auf welcher dynamisch Fragmente platziert werden.
+    * Das Menü, sowie die Headerleiste befinden sich auf der Activity, sind dementsprechend immer verfügbar.
+    *
+    * Zur laufzeit der App wird eine einzige Instanz der Klasse CampusAppContext angelegt.
+    * Dieser Kontext dient als einstiegspunkt für sämmtliche global verfügbaren verwaltungs tools.
+    * Dazu gehören:
+    *   AppContext.getNavigationManager()    NavigationManager   Verwaltet die angezeigten Fragmente und deren Navigation
+    *                                                            .navigatePage("Dashboard")  to navigate to the Dashboard
+    *   ... to be continued ...
+     */
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log.i("CampusApp", "Event: onCreate");
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_campus_app);
+
+        AppContext = CampusAppContext.getInstance();
+        if(AppContext == null)
+            AppContext = new CampusAppContext(this, R.id.fragment_container);
+        else
+            AppContext.setMainActivity(this);
+
+        boolean instantRestore = false;
+        if(savedInstanceState != null) {
+            long lastrun = savedInstanceState.getLong("lastrun");
+            if(((new Date()).getTime()/1000) - lastrun < 30) {
+                instantRestore = true;
+
+                AppContext.setTitle(savedInstanceState.getString("activetitle"));
+
+            }
+        }
+
+        prepareMainUi();
+        if(instantRestore) // orientation change
+            loadMainUi();
+        else
+            // don't add navigation to SlashScreen, or even from SplashScreen to Dashboard to the history log!
+            // it would be very strange to return to the SplashScreen or an "empty" page on return ;)
+            AppContext.getNavigationManager().navigatePage("SplashScreen", null, false);
+
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        Log.i("CampusApp", "Event: onSaveInstanceState");
+        // Save instance state during "restarts" due to orientation changes.
+        // We don't want to see the splash screen everytime the orientation changes ;)
+        savedInstanceState.putLong("lastrun", (new Date()).getTime() / 1000);
+        savedInstanceState.putString("activepage", AppContext.getNavigationManager().getCurrentPageName());
+        TextView titleView = (TextView) findViewById(R.id.title);
+        savedInstanceState.putString("activetitle", titleView.getText().toString());
+
+        // Always call the superclass so it can save the view hierarchy state
+        super.onSaveInstanceState(savedInstanceState);
+    }
+
+    public void prepareMainUi() {
+        ImageView btnOpenSearch = (ImageView) findViewById(R.id.search_button);
+        btnOpenSearch.setVisibility(View.GONE);
+    }
+
+    public void loadMainUi() {
+        ImageView btnOpenSearch = (ImageView) findViewById(R.id.search_button);
+
+        btnOpenSearch.setVisibility(View.VISIBLE);
+        setupActionBar();
+        setupSearchTriggers();
+
+        AppContext.getNfcCardListener().startNfcListener();
+    }
+
+    private void setupActionBar() {
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+        drawer.setDrawerListener(toggle);
+        toggle.syncState();
+
+        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+        navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
+            @SuppressWarnings("StatementWithEmptyBody")
+            @Override
+            public boolean onNavigationItemSelected(MenuItem item) {
+                // Handle navigation view item clicks here.
+                int id = item.getItemId();
+
+                switch (id) { // Navigation Items from res/menu/activity_campus_app_drawer.xml
+                    case R.id.nav_dashboard:
+                        AppContext.getNavigationManager().navigatePage("Dashboard");
+                        break;
+                    case R.id.nav_vorlesungsplan:
+                        AppContext.getNavigationManager().navigatePage("Vorlesungsplan");
+                        break;
+                    case R.id.nav_mensa:
+                        AppContext.getNavigationManager().navigatePage("Mensa");
+                        break;
+                    case R.id.nav_news:
+                        AppContext.getNavigationManager().navigatePage("News");
+                        break;
+                    case R.id.nav_settings:
+                        Intent settings = new Intent(AppContext.getMainActivity(), SettingsActivity.class);
+                        AppContext.getMainActivity().startActivity(settings);
+                        break;
+                    case R.id.nav_impressum:
+                        AppContext.getNavigationManager().navigatePage("Impressum");
+                        break;
+                    default:
+                        return false;
+                }
+
+                DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+                drawer.closeDrawer(GravityCompat.START);
+                return true;
+            }
+        });
+    }
+
+    private void setupSearchTriggers() {
+        ImageView btnOpenSearch = (ImageView) findViewById(R.id.search_button);
+        ImageView btnCloseSearch = (ImageView) findViewById(R.id.search_clear);
+        EditText edtSearchInput = (EditText) findViewById(R.id.search_input);
+        TextView txtTitle = (TextView) findViewById(R.id.title);
+
+        btnOpenSearch.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                triggerSearchPanel(true);
+            }
+        });
+        edtSearchInput.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                EditText edtSearchInput = (EditText) v;
+                if (!hasFocus && edtSearchInput.getText().length() == 0) {
+                    triggerSearchPanel(false);
+                }
+            }
+        });
+        edtSearchInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+                    EditText edtSearchInput = (EditText) v;
+                    triggerSearchAction(edtSearchInput.getText().toString());
+                    triggerSearchPanel(false);
+                    return true;
+                }
+                return false;
+            }
+        });
+        btnCloseSearch.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                EditText edtSearchInput = (EditText) findViewById(R.id.search_input);
+                edtSearchInput.setText("");
+                triggerSearchPanel(false);
+            }
+        });
+        txtTitle.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                triggerSearchPanel(true);
+            }
+        });
+
+    }
+
+    private void triggerSearchPanel(boolean show) {
+        if(bSearchActive == show)
+            return;
+        bSearchActive = show;
+        LinearLayout layTitleContainer = (LinearLayout) findViewById(R.id.title_container);
+        LinearLayout laySearchContainer = (LinearLayout) findViewById(R.id.search_container);
+        EditText edtSearchInput = (EditText) findViewById(R.id.search_input);
+
+        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+        if(show) {
+            layTitleContainer.setVisibility(View.GONE);
+            laySearchContainer.setVisibility(View.VISIBLE);
+            edtSearchInput.requestFocus();
+
+            imm.showSoftInput(edtSearchInput, InputMethodManager.SHOW_IMPLICIT);
+        } else {
+            layTitleContainer.setVisibility(View.VISIBLE);
+            laySearchContainer.setVisibility(View.GONE);
+
+            View view = this.getCurrentFocus();
+            if (view != null) {
+                imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+            }
+        }
+    }
+
+    private void triggerSearchAction(String query) {
+        Bundle bundle = new Bundle();
+        bundle.putString("query", query);
+
+        AppContext.getNavigationManager().navigatePage("AppSearch", bundle);
+    }
+
+    @Override
+    public void onBackPressed() {
+        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+        if (drawer.isDrawerOpen(GravityCompat.START)) {
+            drawer.closeDrawer(GravityCompat.START);
+        } else if(!AppContext.getNavigationManager().back()) {
+            super.onBackPressed(); // trigger system action if internal navigation manager returns false for back()
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.campus_app, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    /* nfc listener related callbacks */
+    @Override
+    public void onResume() {
+        super.onResume();
+        Log.i("CampusApp", "onResume event");
+        AppContext.getNfcCardListener().resumeForefrontDispatcher();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        Log.i("CampusApp", "Event: onPause");
+        AppContext.getNfcCardListener().pauseForefrontDispatcher();
+    }
+
+    @Override
+    public void onNewIntent(Intent intent) {
+        if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
+            AppContext.getNfcCardListener().handleNfcEvent(intent);
+        }
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/CampusAppContext.java b/app/src/main/java/de/dhbwloe/campusapp/CampusAppContext.java
new file mode 100644 (file)
index 0000000..4f42cb5
--- /dev/null
@@ -0,0 +1,159 @@
+package de.dhbwloe.campusapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.lang.reflect.Method;
+
+import de.dhbwloe.campusapp.database.DatabaseManager;
+import de.dhbwloe.campusapp.fragments.AppSearch;
+import de.dhbwloe.campusapp.fragments.Dashboard;
+import de.dhbwloe.campusapp.fragments.FirstRun;
+import de.dhbwloe.campusapp.fragments.Impressum;
+import de.dhbwloe.campusapp.fragments.Mensa;
+import de.dhbwloe.campusapp.fragments.MensaCard;
+import de.dhbwloe.campusapp.fragments.MensaWochenplan;
+import de.dhbwloe.campusapp.fragments.News;
+import de.dhbwloe.campusapp.fragments.PopupFragment;
+import de.dhbwloe.campusapp.fragments.SplashScreen;
+import de.dhbwloe.campusapp.fragments.Vorlesungsplan;
+import de.dhbwloe.campusapp.fragments.WebBrowser;
+import de.dhbwloe.campusapp.fragments.WifiSettings;
+import de.dhbwloe.campusapp.nfcreader.NfcCardInterface;
+import de.dhbwloe.campusapp.nfcreader.NfcCardListener;
+import de.dhbwloe.campusapp.database.NfcCardData;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/** CampusAppContext
+ * A context, that is pushed to each fragment.
+ * This context will be persistent during app execution.
+ */
+public class CampusAppContext {
+    private class AppPage {
+        String name;
+        Class<?> fragment;
+        int fragementType;
+
+        public AppPage(String name, Class<?> fragment) {
+            this.name = name;
+            this.fragment = fragment;
+        }
+
+        public AppPage(String name, Class<?> fragment, int type) {
+            this.name = name;
+            this.fragment = fragment;
+            this.fragementType = type;
+        }
+    }
+    private final AppPage[] PAGES = {
+            new AppPage("SplashScreen", SplashScreen.class),
+            new AppPage("Dashboard", Dashboard.class),
+            new AppPage("AppSearch", AppSearch.class),
+            new AppPage("Vorlesungsplan", Vorlesungsplan.class),
+            new AppPage("Mensa", Mensa.class),
+            new AppPage("MensaCard", MensaCard.class, 3),
+            new AppPage("News", News.class),
+            new AppPage("WifiSettings", WifiSettings.class),
+            new AppPage("FirstRun", FirstRun.class),
+            new AppPage("Impressum", Impressum.class),
+            new AppPage("WebBrowser", WebBrowser.class),
+            new AppPage("WebBrowserPopup", WebBrowser.class, 3)
+    };
+
+    private static CampusAppContext instance;
+    public static CampusAppContext getInstance() {
+        Log.i("AppContext", "Request new context instance");
+        return instance;
+    }
+
+    private Activity oMainActivity;
+    private NavigationManager oNavigationManager;
+    private DatabaseManager oDatabaseManager;
+    private NfcCardListener oNfcCardListener;
+
+    public CampusAppContext(CampusApp mainActivity, int fragmentContainerId) {
+        final CampusAppContext AppContext = this;
+        instance = this;
+        oMainActivity = mainActivity;
+        oNavigationManager = new NavigationManager(this, fragmentContainerId);
+        oDatabaseManager = new DatabaseManager(this);
+        oNfcCardListener = new NfcCardListener(this);
+
+        for(int i = 0; i < PAGES.length; i++)
+            oNavigationManager.registerPage(PAGES[i].name, PAGES[i].fragment, PAGES[i].fragementType);
+
+        oNfcCardListener.registerNfcCardInterface(new NfcCardInterface() {
+            @Override
+            public void onNfcReaderStateChanged(boolean state) {
+
+            }
+
+            @Override
+            public void onNfcReaderReceived(NfcCardData data) {
+                if (data != null)
+                    AppContext.onNfcCardDataReceived(data);
+            }
+        });
+    }
+
+    public Activity getMainActivity() {
+        return oMainActivity;
+    }
+    public void setMainActivity(Activity activity) {
+        oMainActivity = activity;
+    }
+
+    public NavigationManager getNavigationManager() {
+        return oNavigationManager;
+    }
+
+    public void setTitle(String title) {
+        PopupFragment popup = oNavigationManager.getDialog();
+        if(popup != null) {
+            popup.getDialog().setTitle(title);
+        } else {
+            TextView titleView = (TextView)oMainActivity.findViewById(R.id.title);
+            titleView.setText(title);
+        }
+    }
+
+    public DatabaseManager getDatabaseManager() {
+        return oDatabaseManager;
+    }
+
+    public void addDefaultSearchIndexes() {
+        for(int i = 0; i < PAGES.length; i++) {
+            try {
+                Method m = PAGES[i].fragment.getMethod("GetSearchIndices");
+                Object result = m.invoke(null);
+                SearchIndices[] indices = (SearchIndices[]) result;
+                addSearchIndices(indices);
+            } catch (Exception e) {
+            }
+        }
+    }
+
+    public void addSearchIndices(SearchIndices[] indices) {
+        oDatabaseManager.addSearchIndices(indices);
+    }
+
+    public NfcCardListener getNfcCardListener() {
+        return oNfcCardListener;
+    }
+
+    private void onNfcCardDataReceived(NfcCardData data) {
+        Bundle bundle = new Bundle();
+        bundle.putDouble("balance", data.getBalance() / 100.0);
+        bundle.putString("data", data.getCardData());
+
+        String pagename = oNavigationManager.getCurrentPageName();
+        if(pagename != null && pagename.equalsIgnoreCase("MensaCard")) {
+            MensaCard fragment = (MensaCard) oNavigationManager.getCurrentFragment();
+            fragment.showNfcCardData(bundle);
+        } else
+            oNavigationManager.navigatePage("MensaCard", bundle);
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/CampusAppFragment.java b/app/src/main/java/de/dhbwloe/campusapp/CampusAppFragment.java
new file mode 100644 (file)
index 0000000..e0aead8
--- /dev/null
@@ -0,0 +1,26 @@
+package de.dhbwloe.campusapp;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.View;
+
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * Created by pk910 on 19.01.2016.
+ */
+public abstract class CampusAppFragment extends Fragment {
+    protected CampusAppContext AppContext;
+    protected View oFragmentView;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        AppContext = CampusAppContext.getInstance();
+        super.onCreate(savedInstanceState);
+    }
+
+    public static SearchIndices[] GetSearchIndices() {
+        return new SearchIndices[0];
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/NavigationManager.java b/app/src/main/java/de/dhbwloe/campusapp/NavigationManager.java
new file mode 100644 (file)
index 0000000..33d2f1c
--- /dev/null
@@ -0,0 +1,183 @@
+package de.dhbwloe.campusapp;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+
+import de.dhbwloe.campusapp.fragments.PopupFragment;
+
+/**
+ * Created by pk910 on 19.01.2016.
+ */
+public class NavigationManager {
+    private class NavPage {
+        String name;
+        Class<CampusAppFragment> fragmentClass;
+        int fragmentType; // 0 = normal; 1 = fullscreen; 2 = popup
+    };
+
+    private CampusAppContext AppContext;
+    private NavPage oCurrentPage;
+    private NavPage oParentPage;
+    private Fragment oCurrentFragment;
+    private Fragment oParentFragment;
+    private int iFragmentContainerId;
+    private ArrayList<NavPage> lNavigationPages = new ArrayList<NavPage>();
+
+    public NavigationManager(CampusAppContext context, int fragmentContainer) {
+        AppContext = context;
+        iFragmentContainerId = fragmentContainer;
+        oCurrentPage = null;
+    }
+
+    public void registerPage(String name, Class<?> fragment, int fragmentType) {
+        NavPage page = new NavPage();
+        page.name = name;
+        page.fragmentClass = (Class<CampusAppFragment>) fragment;
+        page.fragmentType = fragmentType;
+        lNavigationPages.add(page);
+    }
+
+    public String getCurrentPageName() {
+        if(oCurrentPage == null)
+            return null;
+        return oCurrentPage.name;
+    }
+
+    public CampusAppFragment getCurrentFragment() {
+        if(oCurrentPage == null)
+            return null;
+        if(oCurrentPage.fragmentType != 3)
+            return (CampusAppFragment)oCurrentFragment;
+        return null;
+    }
+
+    public void navigatePage(String name) {
+        navigatePage(name, null, true);
+    }
+
+    public void navigatePage(String name, Bundle args) {
+        navigatePage(name, args, true);
+    }
+
+    public void navigatePage(String name, Bundle args, boolean history) {
+
+        NavPage page = getPageByName(name);
+        if(page == null)
+            return;
+        Fragment fragment;
+
+        if(page.fragmentType == 3) {
+            PopupFragment popupFragment = new PopupFragment();
+            if(args == null)
+                args = new Bundle();
+            args.putString("target", "#"+page.name);
+            fragment = popupFragment;
+        } else {
+            fragment = getFragmentOfPage(page);
+        }
+
+        fragment.setArguments(args);
+
+        FragmentActivity fragmentActivity = (FragmentActivity) AppContext.getMainActivity();
+        FragmentTransaction transaction = fragmentActivity.getSupportFragmentManager().beginTransaction();
+
+        if (oCurrentPage != null && oCurrentPage.fragmentType == 3) {
+            transaction.remove(oCurrentFragment);
+            oCurrentPage = oParentPage;
+            oCurrentFragment = oParentFragment;
+        }
+        if (page.fragmentType == 3) {
+            transaction.add(fragment, "popup");
+            oParentPage = oCurrentPage;
+            oParentFragment = oCurrentFragment;
+            history = false;
+        } else if (oCurrentPage != null) {
+            transaction.replace(iFragmentContainerId, fragment);
+        } else {
+            transaction.add(iFragmentContainerId, fragment);
+        }
+        if (history)
+            transaction.addToBackStack(null);
+
+        oCurrentPage = page;
+        oCurrentFragment = fragment;
+
+        transaction.commit();
+    }
+
+    private NavPage getPageByName(String name) {
+        NavPage page = null;
+        for(int i = 0; i < lNavigationPages.size(); i++) {
+            if(lNavigationPages.get(i).name.equalsIgnoreCase(name)) {
+                page = lNavigationPages.get(i);
+                break;
+            }
+        }
+        if(page == null)
+            return null;
+
+        return page;
+    }
+
+    private CampusAppFragment getFragmentOfPage(NavPage page) {
+        Class<CampusAppFragment> fragmentClass = page.fragmentClass;
+        Constructor fragmentConstructor;
+        CampusAppFragment fragment;
+        try {
+            fragmentConstructor = fragmentClass.asSubclass(fragmentClass).getConstructor();
+            fragment = (CampusAppFragment)fragmentConstructor.newInstance(new Object[]{});
+        } catch (Exception e) {
+            return null;
+        }
+        return fragment;
+    }
+
+    public CampusAppFragment getPageFragment(String name) {
+        NavPage page = getPageByName(name);
+        if(page == null)
+            return null;
+        return getFragmentOfPage(page);
+    }
+
+    public PopupFragment getDialog() {
+        if(oCurrentPage != null && oCurrentPage.fragmentType == 3) {
+            PopupFragment fragment = (PopupFragment) oCurrentFragment;
+            return fragment;
+        }
+        return null;
+    }
+
+    public boolean closeDialog() {
+        if(oCurrentPage != null && oCurrentPage.fragmentType == 3) {
+            PopupFragment fragment = (PopupFragment) oCurrentFragment;
+            fragment.destroyView();
+
+            FragmentActivity fragmentActivity = (FragmentActivity) AppContext.getMainActivity();
+            FragmentTransaction transaction = fragmentActivity.getSupportFragmentManager().beginTransaction();
+
+            transaction.remove(oCurrentFragment);
+
+            oCurrentPage = oParentPage;
+            oCurrentFragment = oParentFragment;
+
+            transaction.commit();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean back() {
+        return closeDialog();
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/SettingsActivity.java b/app/src/main/java/de/dhbwloe/campusapp/SettingsActivity.java
new file mode 100644 (file)
index 0000000..86aedcc
--- /dev/null
@@ -0,0 +1,267 @@
+package de.dhbwloe.campusapp;
+
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.support.v7.app.ActionBar;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.RingtonePreference;
+import android.text.TextUtils;
+import android.view.MenuItem;
+import android.support.v4.app.NavUtils;
+
+import java.util.List;
+
+/**
+ * A {@link PreferenceActivity} that presents a set of application settings. On
+ * handset devices, settings are presented as a single list. On tablets,
+ * settings are split by category, with category headers shown to the left of
+ * the list of settings.
+ * <p/>
+ * See <a href="http://developer.android.com/design/patterns/settings.html">
+ * Android Design: Settings</a> for design guidelines and the <a
+ * href="http://developer.android.com/guide/topics/ui/settings.html">Settings
+ * API Guide</a> for more information on developing a Settings UI.
+ */
+public class SettingsActivity extends AppCompatPreferenceActivity {
+    /**
+     * A preference value change listener that updates the preference's summary
+     * to reflect its new value.
+     */
+    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object value) {
+            String stringValue = value.toString();
+
+            if (preference instanceof ListPreference) {
+                // For list preferences, look up the correct display value in
+                // the preference's 'entries' list.
+                ListPreference listPreference = (ListPreference) preference;
+                int index = listPreference.findIndexOfValue(stringValue);
+
+                // Set the summary to reflect the new value.
+                preference.setSummary(
+                        index >= 0
+                                ? listPreference.getEntries()[index]
+                                : null);
+
+            } else if (preference instanceof RingtonePreference) {
+                // For ringtone preferences, look up the correct display value
+                // using RingtoneManager.
+                if (TextUtils.isEmpty(stringValue)) {
+                    // Empty values correspond to 'silent' (no ringtone).
+                    preference.setSummary(R.string.pref_ringtone_silent);
+
+                } else {
+                    Ringtone ringtone = RingtoneManager.getRingtone(
+                            preference.getContext(), Uri.parse(stringValue));
+
+                    if (ringtone == null) {
+                        // Clear the summary if there was a lookup error.
+                        preference.setSummary(null);
+                    } else {
+                        // Set the summary to reflect the new ringtone display
+                        // name.
+                        String name = ringtone.getTitle(preference.getContext());
+                        preference.setSummary(name);
+                    }
+                }
+
+            } else {
+                // For all other preferences, set the summary to the value's
+                // simple string representation.
+                preference.setSummary(stringValue);
+            }
+            return true;
+        }
+    };
+
+    /**
+     * Helper method to determine if the device has an extra-large screen. For
+     * example, 10" tablets are extra-large.
+     */
+    private static boolean isXLargeTablet(Context context) {
+        return (context.getResources().getConfiguration().screenLayout
+                & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
+    }
+
+    /**
+     * Binds a preference's summary to its value. More specifically, when the
+     * preference's value is changed, its summary (line of text below the
+     * preference title) is updated to reflect the value. The summary is also
+     * immediately updated upon calling this method. The exact display format is
+     * dependent on the type of preference.
+     *
+     * @see #sBindPreferenceSummaryToValueListener
+     */
+    private static void bindPreferenceSummaryToValue(Preference preference) {
+        // Set the listener to watch for value changes.
+        preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
+
+        // Trigger the listener immediately with the preference's
+        // current value.
+        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
+                PreferenceManager
+                        .getDefaultSharedPreferences(preference.getContext())
+                        .getString(preference.getKey(), ""));
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setupActionBar();
+    }
+
+    /**
+     * Set up the {@link android.app.ActionBar}, if the API is available.
+     */
+    private void setupActionBar() {
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            // Show the Up button in the action bar.
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        int id = item.getItemId();
+        if (id == android.R.id.home) {
+            if (!super.onMenuItemSelected(featureId, item)) {
+                NavUtils.navigateUpFromSameTask(this);
+            }
+            return true;
+        }
+        return super.onMenuItemSelected(featureId, item);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean onIsMultiPane() {
+        return isXLargeTablet(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.pref_headers, target);
+    }
+
+    /**
+     * This method stops fragment injection in malicious applications.
+     * Make sure to deny any unknown fragments here.
+     */
+    protected boolean isValidFragment(String fragmentName) {
+        return PreferenceFragment.class.getName().equals(fragmentName)
+                || GeneralPreferenceFragment.class.getName().equals(fragmentName)
+                || DataSyncPreferenceFragment.class.getName().equals(fragmentName)
+                || NotificationPreferenceFragment.class.getName().equals(fragmentName);
+    }
+
+    /**
+     * This fragment shows general preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class GeneralPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_general);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+            bindPreferenceSummaryToValue(findPreference("example_text"));
+            bindPreferenceSummaryToValue(findPreference("example_list"));
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    /**
+     * This fragment shows notification preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class NotificationPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_notification);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+            bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    /**
+     * This fragment shows data and sync preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class DataSyncPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_data_sync);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+            bindPreferenceSummaryToValue(findPreference("sync_frequency"));
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/Tools.java b/app/src/main/java/de/dhbwloe/campusapp/Tools.java
new file mode 100644 (file)
index 0000000..02c8d7a
--- /dev/null
@@ -0,0 +1,54 @@
+package de.dhbwloe.campusapp;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Created by pk910 on 25.01.2016.
+ */
+public class Tools {
+
+    public static final String md5(final String s) {
+        final String MD5 = "MD5";
+        try {
+            // Create MD5 Hash
+            MessageDigest digest = java.security.MessageDigest.getInstance(MD5);
+            digest.update(s.getBytes());
+            byte messageDigest[] = digest.digest();
+
+            // Create Hex String
+            StringBuilder hexString = new StringBuilder();
+            for (byte aMessageDigest : messageDigest) {
+                String h = Integer.toHexString(0xFF & aMessageDigest);
+                while (h.length() < 2)
+                    h = "0" + h;
+                hexString.append(h);
+            }
+            return hexString.toString();
+
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    public static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
+        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
+        Cipher cipher = Cipher.getInstance("AES");
+        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
+        byte[] encrypted = cipher.doFinal(clear);
+        return encrypted;
+    }
+
+    public static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
+        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
+        Cipher cipher = Cipher.getInstance("AES");
+        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
+        byte[] decrypted = cipher.doFinal(encrypted);
+        return decrypted;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/database/DatabaseManager.java b/app/src/main/java/de/dhbwloe/campusapp/database/DatabaseManager.java
new file mode 100644 (file)
index 0000000..43a5fd5
--- /dev/null
@@ -0,0 +1,402 @@
+package de.dhbwloe.campusapp.database;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import net.fortuna.ical4j.model.DateList;
+import net.fortuna.ical4j.model.DateTime;
+import net.fortuna.ical4j.model.Period;
+import net.fortuna.ical4j.model.Recur;
+import net.fortuna.ical4j.model.parameter.Value;
+import net.fortuna.ical4j.model.property.RRule;
+
+import java.lang.reflect.Array;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.ListIterator;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.mensaplan.MensaTagesplan;
+import de.dhbwloe.campusapp.news.NewsItem;
+import de.dhbwloe.campusapp.search.SearchIndices;
+import de.dhbwloe.campusapp.vorlesungen.CourseEvent;
+import de.dhbwloe.campusapp.vorlesungen.CourseGroup;
+
+/**
+ * Created by pk910 on 19.01.2016.
+ */
+public class DatabaseManager {
+    private static final String DATABASE_NAME = "DHBWLoe.CampusApp.DEV";
+    private static final int DATABASE_VERSION = 1;
+    private CampusAppContext AppContext;
+    private SQLiteDatabase database;
+    private NewsDatabaseHelper newsDBHelper;
+    private MensaplanDatabaseHelper mensaplanDBHelper;
+    private VorlesungsplanDatabaseHelper vorlesungsplanDBHelper;
+
+
+    public DatabaseManager(CampusAppContext context) {
+        AppContext = context;
+    }
+
+    public void initializeDatabase() {
+        database = AppContext.getMainActivity().openOrCreateDatabase(DATABASE_NAME, Activity.MODE_PRIVATE, null);
+        database.execSQL("CREATE TABLE IF NOT EXISTS Version(Version INT);");
+
+        Cursor resultSet = database.rawQuery("Select * from Version", null);
+        int version;
+        if(resultSet.moveToFirst()) {
+            version = resultSet.getInt(0);
+        } else {
+            version = 0;
+            database.execSQL("INSERT INTO Version (Version) VALUES (0);");
+        }
+
+        resultSet.close();
+        if(version < DATABASE_VERSION)
+            upgradeTables(version, DATABASE_VERSION);
+
+
+    }
+
+    private void upgradeTables(int oldVersion, int newVersion) {
+        if(oldVersion == 0 && newVersion > 0) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS RuntimeCache " +
+                    "(" +
+                    "Reference TEXT, " +
+                    "Value TEXT, " +
+                    "LastUpdate INT, " +
+                    "PRIMARY KEY (Reference)" +
+                    ");");
+            database.execSQL("CREATE TABLE IF NOT EXISTS SearchIndex " +
+                    "(" +
+                    "KeyName TEXT, " +
+                    "SearchText TEXT, " +
+                    "SearchTitle TEXT," +
+                    "Description TEXT, " +
+                    "StaticEntry INT, " +
+                    "UpdateTime INT, " +
+                    "TargetPage TEXT);");
+            database.execSQL("CREATE TABLE IF NOT EXISTS CourseCalendar " +
+                    "(" +
+                    "Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
+                    "CourseName TEXT, " +
+                    "UniqueId TEXT, " +
+                    "SequenceId INT, " +
+                    "EventFrom INT, " +
+                    "EventTo INT, " +
+                    "EventTitle TEXT, " +
+                    "EventLocation TEXT, " +
+                    "EventStatus TEXT," +
+                    "RecurRule TEXT," +
+                    "ExcludeDates TEXT," +
+                    "CourseGroupId INT," +
+                    "UNIQUE (CourseName, UniqueId)" +
+                    ");");
+            database.execSQL("CREATE TABLE IF NOT EXISTS CourseCalendarEvent " +
+                    "(" +
+                    "EventId INT, " +
+                    "EventFrom INT, " +
+                    "EventTo INT, " +
+                    "PRIMARY KEY (EventId, EventFrom, EventTo)" +
+                    ");");
+            database.execSQL("CREATE INDEX CourseCalendarEventIdx ON CourseCalendarEvent (EventFrom, EventTo);");
+            database.execSQL("CREATE TABLE IF NOT EXISTS NfcCardStore " +
+                    "(" +
+                    "CardId INT, " +
+                    "UpdateTime INT," +
+                    "CardData TEXT, " +
+                    "CardBalance INT, " +
+                    "CardLastTransaction INT, " +
+                    "PRIMARY KEY (CardId, UpdateTime)" +
+                    ");");
+            database.execSQL("CREATE TABLE IF NOT EXISTS CourseCalendarGroup " +
+                    "(" +
+                    "GroupId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
+                    "CourseName INT," +
+                    "GroupName TEXT, " +
+                    "LastUpdate INT, " +
+                    "UNIQUE (GroupName)" +
+                    ");");
+            database.execSQL("CREATE INDEX CourseCalendarGroupIdx ON CourseCalendarGroup (CourseName, GroupName);");
+            database.execSQL("CREATE TABLE IF NOT EXISTS MensaPlan " +
+                    "(" +
+                    "PlanDate INT, " +
+                    "MenuName TEXT, " +
+                    "ChkSum INT, " +
+                    "Name TEXT, " +
+                    "NameHtml TEXT, " +
+                    "Additional TEXT, " +
+                    "Notes TEXT, " +
+                    "PriceStudents INT, " +
+                    "PriceEmployees INT, " +
+                    "PriceGuests INT, " +
+                    "PriceSchool INT, " +
+                    "PRIMARY KEY (PlanDate, MenuName)" +
+                    ");");
+            database.execSQL("CREATE TABLE IF NOT EXISTS News " +
+                    "(" +
+                    "Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
+                    "Source TEXT, " +
+                    "Time INT, " +
+                    "UniqueId TEXT, " +
+                    "ChkSum INT, " +
+                    "Title TEXT, " +
+                    "Summary TEXT, " +
+                    "Content TEXT, " +
+                    "Link TEXT, " +
+                    "Categories TEXT, " +
+                    "UNIQUE(Source, UniqueId) " +
+                    ");");
+            database.execSQL("CREATE INDEX NewsIdx ON News (Source, Time);");
+        }
+        if(oldVersion < 2 && newVersion >= 2) {
+            // Version 2
+
+        }
+
+        database.execSQL("UPDATE Version SET Version = "+Integer.toString(newVersion));
+    }
+
+    public void addSearchIndices(SearchIndices[] indices) {
+        for(int i = 0; i < indices.length; i++) {
+            String[] whereArgs = new String[] {
+                    indices[i].getKeyName()
+            };
+            Cursor resultSet = database.rawQuery("SELECT UpdateTime FROM SearchIndex WHERE KeyName = ?", whereArgs);
+            if(resultSet.moveToFirst()) {
+                long updateTime = resultSet.getLong(0);
+                if (updateTime < indices[i].getUpdateTime()) {
+                    // remove
+                    database.rawQuery("DELETE FROM SearchIndex WHERE KeyName = ?", whereArgs);
+                } else
+                    continue;
+            }
+            resultSet.close();
+            // add new
+            try {
+                ContentValues indexValues = new ContentValues();
+                indexValues.put("KeyName", indices[i].getKeyName());
+                indexValues.put("SearchText", indices[i].getKeyWords());
+                indexValues.put("SearchTitle", indices[i].getTitle());
+                indexValues.put("Description", indices[i].getDescription());
+                indexValues.put("StaticEntry", indices[i].getIsStatic() ? 1 : 0);
+                indexValues.put("UpdateTime", indices[i].getUpdateTime());
+                indexValues.put("TargetPage", indices[i].getTarget());
+
+                database.insertOrThrow("SearchIndex", null, indexValues);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public SearchIndices[] performSearchRequest(String query, int maxResults) {
+        String[] whereArgs = new String[] {
+                "%" + query + "%"
+        };
+        Cursor resultSet = database.rawQuery("SELECT KeyName, SearchTitle, Description, StaticEntry, UpdateTime, TargetPage, SearchText FROM SearchIndex WHERE SearchText LIKE ? ORDER BY StaticEntry DESC, UpdateTime DESC", whereArgs);
+        ArrayList<SearchIndices> indices = new ArrayList<SearchIndices>();
+        if(resultSet.moveToFirst()) {
+            int[] columnIndexes = {
+                    resultSet.getColumnIndex("KeyName"),
+                    resultSet.getColumnIndex("SearchTitle"),
+                    resultSet.getColumnIndex("Description"),
+                    resultSet.getColumnIndex("StaticEntry"),
+                    resultSet.getColumnIndex("UpdateTime"),
+                    resultSet.getColumnIndex("TargetPage"),
+                    resultSet.getColumnIndex("SearchText")
+            };
+            do {
+                SearchIndices cIndices = new SearchIndices(resultSet.getString(columnIndexes[0]), (resultSet.getInt(columnIndexes[3]) == 1));
+                cIndices.setUpdateTime(resultSet.getLong(columnIndexes[4]));
+                cIndices.setTarget(resultSet.getString(columnIndexes[5]));
+                cIndices.addKeyWord(resultSet.getString(columnIndexes[6]));
+                cIndices.setTitle(resultSet.getString(columnIndexes[1]));
+                cIndices.setDescription(resultSet.getString(columnIndexes[2]));
+                indices.add(cIndices);
+            } while (resultSet.moveToNext() && indices.size() < maxResults);
+        }
+        resultSet.close();
+
+        SearchIndices[] indicesArr = new SearchIndices[indices.size()];
+        indicesArr = indices.toArray(indicesArr);
+        return indicesArr;
+    }
+
+    public void setRuntimeCache(String name, String value) {
+        long now = (new Date()).getTime() / 1000;
+        String[] whereArgs = new String[] {
+                name
+        };
+        Cursor resultSet = database.rawQuery("SELECT Value FROM RuntimeCache WHERE Reference = ?", whereArgs);
+        if(resultSet.moveToFirst()) {
+            if(resultSet.getString(0).equalsIgnoreCase(value))
+                return;
+            try {
+                ContentValues updateValues = new ContentValues();
+                updateValues.put("Value", value);
+                updateValues.put("LastUpdate", now);
+
+                database.update("RuntimeCache", updateValues, "Reference = ?", whereArgs);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        } else {
+            try {
+                ContentValues indexValues = new ContentValues();
+                indexValues.put("Reference", name);
+                indexValues.put("Value", value);
+                indexValues.put("LastUpdate", now);
+
+                database.insertOrThrow("RuntimeCache", null, indexValues);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        }
+        resultSet.close();
+    }
+
+    public String getRuntimeCache(String name) {
+        String value = null;
+        String[] whereArgs = new String[] {
+                name
+        };
+        Cursor resultSet = database.rawQuery("SELECT Value FROM RuntimeCache WHERE Reference = ?", whereArgs);
+        if(resultSet.moveToFirst()) {
+            value = resultSet.getString(0);
+        }
+        resultSet.close();
+        return value;
+    }
+
+    public void addNfcCardData(NfcCardData nfcCardData) {
+        String[] whereArgs = new String[] {
+                Integer.toString(nfcCardData.getUniqueId()),
+                Long.toString(nfcCardData.getLastUpdate())
+        };
+        Cursor resultSet = database.rawQuery("SELECT CardData FROM NfcCardStore WHERE CardId = ? AND UpdateTime = ?", whereArgs);
+        if(resultSet.moveToFirst()) {
+            if(resultSet.getString(0).equalsIgnoreCase(nfcCardData.getCardData()))
+                return;
+            try {
+                ContentValues updateValues = new ContentValues();
+                updateValues.put("CardData", nfcCardData.getCardData());
+                updateValues.put("CardBalance", nfcCardData.getBalance());
+                updateValues.put("CardLastTransaction", nfcCardData.getLastTransaction());
+
+                database.update("NfcCardStore", updateValues, "CardId = ? AND UpdateTime = ?", whereArgs);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        } else {
+            try {
+                ContentValues indexValues = new ContentValues();
+                indexValues.put("CardId", nfcCardData.getUniqueId());
+                indexValues.put("UpdateTime", nfcCardData.getLastUpdate());
+                indexValues.put("CardData", nfcCardData.getCardData());
+                indexValues.put("CardBalance", nfcCardData.getBalance());
+                indexValues.put("CardLastTransaction", nfcCardData.getLastTransaction());
+
+                database.insertOrThrow("NfcCardStore", null, indexValues);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        }
+        resultSet.close();
+    }
+
+    public NfcCardData[] getNfcCardData(int lastLimit) {
+        String value = null;
+        Cursor resultSet;
+        if(lastLimit > 0) {
+            String[] whereArgs = {
+                    Integer.toString(lastLimit)
+            };
+            resultSet = database.rawQuery("SELECT CardId,UpdateTime,CardData,CardBalance,CardLastTransaction FROM NfcCardStore ORDER BY UpdateTime DESC LIMIT ?", whereArgs);
+        } else
+            resultSet = database.rawQuery("SELECT CardId,UpdateTime,CardData,CardBalance,CardLastTransaction FROM NfcCardStore ORDER BY UpdateTime DESC", null);
+        ArrayList<NfcCardData> nfcCardDatas = new ArrayList<NfcCardData>();
+        if(resultSet.moveToFirst()) {
+            do {
+                NfcCardData nfcCardData = new NfcCardData(resultSet.getInt(0), resultSet.getLong(1), resultSet.getString(2), resultSet.getInt(3), resultSet.getInt(4));
+                nfcCardDatas.add(nfcCardData);
+            } while (resultSet.moveToNext());
+        }
+        resultSet.close();
+        NfcCardData[] resultsArr = new NfcCardData[nfcCardDatas.size()];
+        resultsArr = nfcCardDatas.toArray(resultsArr);
+        return resultsArr;
+    }
+
+    public void updateCourseCalendar(CourseEvent event) {
+        if(vorlesungsplanDBHelper == null)
+            vorlesungsplanDBHelper = new VorlesungsplanDatabaseHelper(AppContext, database);
+        vorlesungsplanDBHelper.updateCourseCalendar(event);
+    }
+
+    public CourseEvent[] getCourseCalendarEvents(String coursename, long timeFrom, long timeTo) {
+        if(vorlesungsplanDBHelper == null)
+            vorlesungsplanDBHelper = new VorlesungsplanDatabaseHelper(AppContext, database);
+        return vorlesungsplanDBHelper.getCourseCalendarEvents(coursename, timeFrom, timeTo);
+    }
+
+    public CourseGroup getCourseGroup(int courseGroupId) {
+        if(vorlesungsplanDBHelper == null)
+            vorlesungsplanDBHelper = new VorlesungsplanDatabaseHelper(AppContext, database);
+        return vorlesungsplanDBHelper.getCourseGroup(courseGroupId);
+    }
+
+    public CourseGroup getCourseGroup(String coursename, String groupname) {
+        if(vorlesungsplanDBHelper == null)
+            vorlesungsplanDBHelper = new VorlesungsplanDatabaseHelper(AppContext, database);
+        return vorlesungsplanDBHelper.getCourseGroup(coursename, groupname);
+    }
+
+    public CourseGroup addCourseGroup(String coursename, String groupname) {
+        if(vorlesungsplanDBHelper == null)
+            vorlesungsplanDBHelper = new VorlesungsplanDatabaseHelper(AppContext, database);
+        return vorlesungsplanDBHelper.addCourseGroup(coursename, groupname);
+    }
+
+    public void updateMensaTagesplan(MensaTagesplan plan) {
+        if(mensaplanDBHelper == null)
+            mensaplanDBHelper = new MensaplanDatabaseHelper(AppContext, database);
+        mensaplanDBHelper.updateMensaTagesplan(plan);
+    }
+
+    public MensaTagesplan[] getMensaTagesplan(long timeFrom, long timeTo) {
+        if(mensaplanDBHelper == null)
+            mensaplanDBHelper = new MensaplanDatabaseHelper(AppContext, database);
+        return mensaplanDBHelper.getMensaTagesplan(timeFrom, timeTo);
+    }
+
+    public long[] getDaysWithPlanData(long timeFrom, long timeTo) {
+        if(mensaplanDBHelper == null)
+            mensaplanDBHelper = new MensaplanDatabaseHelper(AppContext, database);
+        return mensaplanDBHelper.getDaysWithPlanData(timeFrom, timeTo);
+    }
+
+    public long[] getWeeksWithPlanData(long timeFrom, long timeTo) {
+        if(mensaplanDBHelper == null)
+            mensaplanDBHelper = new MensaplanDatabaseHelper(AppContext, database);
+        return mensaplanDBHelper.getWeeksWithPlanData(timeFrom, timeTo);
+    }
+
+    public void updateNewsItem(NewsItem news) {
+        if(newsDBHelper == null)
+            newsDBHelper = new NewsDatabaseHelper(AppContext, database);
+        newsDBHelper.updateNewsItem(news);
+    }
+
+    public NewsItem[] getNewsItems(String source, long timeFrom, long timeTo) {
+        if(newsDBHelper == null)
+            newsDBHelper = new NewsDatabaseHelper(AppContext, database);
+        return newsDBHelper.getNewsItems(source, timeFrom, timeTo);
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/database/MensaplanDatabaseHelper.java b/app/src/main/java/de/dhbwloe/campusapp/database/MensaplanDatabaseHelper.java
new file mode 100644 (file)
index 0000000..a8a6fd4
--- /dev/null
@@ -0,0 +1,170 @@
+package de.dhbwloe.campusapp.database;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.util.ArrayList;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.mensaplan.MensaTagesplan;
+
+/**
+ * Created by pk910 on 24.01.2016.
+ */
+public class MensaplanDatabaseHelper {
+    private CampusAppContext AppContext;
+    private SQLiteDatabase database;
+
+    public MensaplanDatabaseHelper(CampusAppContext context, SQLiteDatabase database) {
+        this.database = database;
+        AppContext = context;
+    }
+
+    public void updateMensaTagesplan(MensaTagesplan plan) {
+        boolean isExisting = false;
+        String[] whereArgs = new String[] {
+                Long.toString(plan.getPlanDate()),
+                plan.getMenuName()
+        };
+        Cursor resultSet = database.rawQuery("SELECT ChkSum FROM MensaPlan WHERE PlanDate = ? AND MenuName = ?", whereArgs);
+        if(resultSet.moveToFirst()) {
+            long chksum = resultSet.getLong(0);
+            if(plan.getChkSum() == chksum)
+                return; // nothing to update
+            isExisting = true;
+        }
+        resultSet.close();
+
+        if(isExisting) {
+            try {
+                ContentValues updateValues = new ContentValues();
+                updateValues.put("ChkSum", plan.getChkSum());
+                updateValues.put("Name", plan.getName());
+                updateValues.put("NameHtml", plan.getNameHtml());
+                updateValues.put("Additional", plan.getAdditional());
+                updateValues.put("Notes", plan.getNotes());
+                int plainPrice[] = plan.getPlainPrice();
+                updateValues.put("PriceStudents", plainPrice[0]);
+                updateValues.put("PriceEmployees", plainPrice[1]);
+                updateValues.put("PriceGuests", plainPrice[2]);
+                updateValues.put("PriceSchool", plainPrice[3]);
+
+                database.update("MensaPlan", updateValues, "PlanDate = ? AND MenuName = ?", whereArgs);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        } else {
+            try {
+                ContentValues indexValues = new ContentValues();
+                indexValues.put("PlanDate", plan.getPlanDate());
+                indexValues.put("MenuName", plan.getMenuName());
+
+                indexValues.put("ChkSum", plan.getChkSum());
+                indexValues.put("Name", plan.getName());
+                indexValues.put("NameHtml", plan.getNameHtml());
+                indexValues.put("Additional", plan.getAdditional());
+                indexValues.put("Notes", plan.getNotes());
+                int plainPrice[] = plan.getPlainPrice();
+                indexValues.put("PriceStudents", plainPrice[0]);
+                indexValues.put("PriceEmployees", plainPrice[1]);
+                indexValues.put("PriceGuests", plainPrice[2]);
+                indexValues.put("PriceSchool", plainPrice[3]);
+
+                database.insertOrThrow("MensaPlan", null, indexValues);
+                plan.setIsNew();
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+    }
+
+    public MensaTagesplan[] getMensaTagesplan(long timeFrom, long timeTo) {
+        String[] whereArgs = new String[] {
+                Long.toString(timeFrom),
+                Long.toString(timeTo)
+        };
+        Cursor resultSet = database.rawQuery("SELECT PlanDate,MenuName,ChkSum,Name,NameHtml,Additional,Notes,PriceStudents,PriceEmployees,PriceGuests,PriceSchool FROM MensaPlan WHERE PlanDate >= ? AND PlanDate <= ?", whereArgs);
+        ArrayList<MensaTagesplan> results = new ArrayList<MensaTagesplan>();
+        if(resultSet.moveToFirst()) {
+            int[] columnIndexes = {
+                    resultSet.getColumnIndex("PlanDate"),
+                    resultSet.getColumnIndex("MenuName"),
+                    resultSet.getColumnIndex("ChkSum"),
+                    resultSet.getColumnIndex("Name"),
+                    resultSet.getColumnIndex("NameHtml"),
+                    resultSet.getColumnIndex("Additional"),
+                    resultSet.getColumnIndex("Notes"),
+                    resultSet.getColumnIndex("PriceStudents"),
+                    resultSet.getColumnIndex("PriceEmployees"),
+                    resultSet.getColumnIndex("PriceGuests"),
+                    resultSet.getColumnIndex("PriceSchool")
+            };
+            do {
+                MensaTagesplan plan = new MensaTagesplan(
+                        resultSet.getLong(columnIndexes[0]), resultSet.getString(columnIndexes[1]),
+                        resultSet.getLong(columnIndexes[2]), resultSet.getString(columnIndexes[3]),
+                        resultSet.getString(columnIndexes[4]), resultSet.getString(columnIndexes[5]),
+                        resultSet.getString(columnIndexes[6]),
+                        resultSet.getInt(columnIndexes[7]), resultSet.getInt(columnIndexes[8]),
+                        resultSet.getInt(columnIndexes[9]), resultSet.getInt(columnIndexes[10])
+                );
+
+                results.add(plan);
+            } while (resultSet.moveToNext());
+        }
+        resultSet.close();
+
+        MensaTagesplan[] resultsArr = new MensaTagesplan[results.size()];
+        resultsArr = results.toArray(resultsArr);
+        return resultsArr;
+    }
+
+    public long[] getDaysWithPlanData(long timeFrom, long timeTo) {
+        String[] whereArgs = new String[] {
+                Long.toString(timeFrom),
+                Long.toString(timeTo)
+        };
+        Cursor resultSet = database.rawQuery("SELECT PlanDate FROM MensaPlan WHERE PlanDate >= ? AND PlanDate <= ?  GROUP BY PlanDate", whereArgs);
+        ArrayList<Long> results = new ArrayList<Long>();
+        if(resultSet.moveToFirst()) {
+            do {
+                long date = resultSet.getLong(0);
+
+                results.add(date);
+            } while (resultSet.moveToNext());
+        }
+        resultSet.close();
+
+        long[] resultsArr = new long[results.size()];
+        for(int i = 0; i < resultsArr.length; i++) {
+            resultsArr[i] = results.get(i);
+        }
+        return resultsArr;
+    }
+
+    public long[] getWeeksWithPlanData(long timeFrom, long timeTo) {
+        String[] whereArgs = new String[] {
+                Long.toString(timeFrom),
+                Long.toString(timeTo)
+        };
+        Cursor resultSet = database.rawQuery("SELECT MIN(PlanDate) AS PlanDate, strftime(\"%W\", PlanDate, \"unixepoch\") AS PlanWeek FROM MensaPlan WHERE PlanDate >= ? AND PlanDate <= ?  GROUP BY PlanWeek", whereArgs);
+        ArrayList<Long> results = new ArrayList<Long>();
+        if(resultSet.moveToFirst()) {
+            do {
+                long date = resultSet.getLong(0);
+
+                results.add(date);
+            } while (resultSet.moveToNext());
+        }
+        resultSet.close();
+
+        long[] resultsArr = new long[results.size()];
+        for(int i = 0; i < resultsArr.length; i++) {
+            resultsArr[i] = results.get(i);
+        }
+        return resultsArr;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/database/NewsDatabaseHelper.java b/app/src/main/java/de/dhbwloe/campusapp/database/NewsDatabaseHelper.java
new file mode 100644 (file)
index 0000000..e71066d
--- /dev/null
@@ -0,0 +1,117 @@
+package de.dhbwloe.campusapp.database;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.util.ArrayList;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.news.NewsItem;
+
+/**
+ * Created by pk910 on 24.01.2016.
+ */
+public class NewsDatabaseHelper {
+    private CampusAppContext AppContext;
+    private SQLiteDatabase database;
+
+    public NewsDatabaseHelper(CampusAppContext context, SQLiteDatabase database) {
+        this.database = database;
+        AppContext = context;
+    }
+
+    public void updateNewsItem(NewsItem news) {
+        boolean isExisting = false;
+        String[] whereArgs = new String[] {
+                news.getSource(),
+                news.getUniqueId()
+        };
+        Cursor resultSet = database.rawQuery("SELECT ChkSum FROM News WHERE Source = ? AND UniqueId = ?", whereArgs);
+        if(resultSet.moveToFirst()) {
+            long chksum = resultSet.getLong(0);
+            if (news.getChkSum() == chksum)
+                return; // nothing to update
+            isExisting = true;
+        }
+        resultSet.close();
+
+        if(isExisting) {
+            try {
+                ContentValues updateValues = new ContentValues();
+                updateValues.put("ChkSum", news.getChkSum());
+                updateValues.put("Title", news.getTitle());
+                updateValues.put("Time", news.getTime());
+                updateValues.put("Summary", news.getSummary());
+                updateValues.put("Content", news.getContent());
+                updateValues.put("Link", news.getLink());
+                updateValues.put("Categories", news.getCategories());
+
+                database.update("News", updateValues, "Source = ? AND UniqueId = ?", whereArgs);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        } else {
+            try {
+                ContentValues indexValues = new ContentValues();
+                indexValues.put("Source", news.getSource());
+                indexValues.put("UniqueId", news.getUniqueId());
+
+                indexValues.put("ChkSum", news.getChkSum());
+                indexValues.put("Title", news.getTitle());
+                indexValues.put("Time", news.getTime());
+                indexValues.put("Summary", news.getSummary());
+                indexValues.put("Content", news.getContent());
+                indexValues.put("Link", news.getLink());
+                indexValues.put("Categories", news.getCategories());
+
+                long newId = database.insertOrThrow("News", null, indexValues);
+                news.setIsNew((int) newId);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+    }
+
+    public NewsItem[] getNewsItems(String source, long timeFrom, long timeTo) {
+        String[] whereArgs = new String[] {
+                source,
+                Long.toString(timeFrom),
+                Long.toString(timeTo)
+        };
+        Cursor resultSet = database.rawQuery("SELECT Id, Source, Time, UniqueId, ChkSum, Title, Summary, Content, Link, Categories FROM News WHERE Source = ? AND Time >= ? AND Time <= ?", whereArgs);
+        ArrayList<NewsItem> results = new ArrayList<NewsItem>();
+        if(resultSet.moveToFirst()) {
+            int[] columnIndexes = {
+                    resultSet.getColumnIndex("Id"),
+                    resultSet.getColumnIndex("Source"),
+                    resultSet.getColumnIndex("Time"),
+                    resultSet.getColumnIndex("UniqueId"),
+                    resultSet.getColumnIndex("ChkSum"),
+                    resultSet.getColumnIndex("Title"),
+                    resultSet.getColumnIndex("Summary"),
+                    resultSet.getColumnIndex("Content"),
+                    resultSet.getColumnIndex("Link"),
+                    resultSet.getColumnIndex("Categories")
+            };
+            do {
+                NewsItem news = new NewsItem(
+                        resultSet.getInt(columnIndexes[0]), resultSet.getString(columnIndexes[1]),
+                        resultSet.getLong(columnIndexes[2]), resultSet.getString(columnIndexes[3]),
+                        resultSet.getLong(columnIndexes[4]), resultSet.getString(columnIndexes[5]),
+                        resultSet.getString(columnIndexes[6]), resultSet.getString(columnIndexes[7]),
+                        resultSet.getString(columnIndexes[8]), resultSet.getString(columnIndexes[9])
+                );
+
+                results.add(news);
+            } while (resultSet.moveToNext());
+        }
+        resultSet.close();
+
+        NewsItem[] resultsArr = new NewsItem[results.size()];
+        resultsArr = results.toArray(resultsArr);
+        return resultsArr;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/database/NfcCardData.java b/app/src/main/java/de/dhbwloe/campusapp/database/NfcCardData.java
new file mode 100644 (file)
index 0000000..08a6e2d
--- /dev/null
@@ -0,0 +1,51 @@
+package de.dhbwloe.campusapp.database;
+
+import java.util.Date;
+
+/**
+ * Created by pk910 on 21.01.2016.
+ */
+public class NfcCardData {
+    private int iUniqueId;
+    private long iLastUpdate;
+    private String sCardData;
+    private int iBalance;
+    private int iLastTransaction;
+
+    public NfcCardData(de.dhbwloe.campusapp.nfcreader.cardreader.NfcCardData carddata) {
+        iUniqueId = carddata.getUniqueid();
+        iLastUpdate = (new Date()).getTime()/1000;
+        sCardData = carddata.getCompactCardDataSummary();
+        iBalance = (int)(carddata.getBalanceData() * 100);
+        iLastTransaction = (int)(carddata.getLastTransaction() * 100);
+    }
+
+    public NfcCardData(int uid, long lastupdate, String carddata, int balance, int transaction) {
+        iUniqueId = uid;
+        iLastUpdate = lastupdate;
+        sCardData = carddata;
+        iBalance = balance;
+        iLastTransaction = transaction;
+    }
+
+    public int getUniqueId() {
+        return iUniqueId;
+    }
+
+    public String getCardData() {
+        return sCardData;
+    }
+
+    public long getLastUpdate() {
+        return iLastUpdate;
+    }
+
+    public int getBalance() {
+        return iBalance;
+    }
+
+    public int getLastTransaction() {
+        return iLastTransaction;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/database/VorlesungsplanDatabaseHelper.java b/app/src/main/java/de/dhbwloe/campusapp/database/VorlesungsplanDatabaseHelper.java
new file mode 100644 (file)
index 0000000..e38b87d
--- /dev/null
@@ -0,0 +1,245 @@
+package de.dhbwloe.campusapp.database;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import net.fortuna.ical4j.model.DateList;
+import net.fortuna.ical4j.model.DateTime;
+import net.fortuna.ical4j.model.Period;
+import net.fortuna.ical4j.model.Recur;
+import net.fortuna.ical4j.model.parameter.Value;
+import net.fortuna.ical4j.model.property.RRule;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.vorlesungen.CourseEvent;
+import de.dhbwloe.campusapp.vorlesungen.CourseGroup;
+
+/**
+ * Created by pk910 on 24.01.2016.
+ */
+public class VorlesungsplanDatabaseHelper {
+    private CampusAppContext AppContext;
+    private SQLiteDatabase database;
+
+    public VorlesungsplanDatabaseHelper(CampusAppContext context, SQLiteDatabase database) {
+        this.database = database;
+        AppContext = context;
+    }
+
+
+    public void updateCourseCalendar(CourseEvent event) {
+        boolean isExisting = false;
+        String[] whereArgs = new String[] {
+                event.getCourseName(),
+                event.getUniqueId()
+        };
+        Cursor resultSet = database.rawQuery("SELECT SequenceId FROM CourseCalendar WHERE CourseName = ? AND UniqueId = ?", whereArgs);
+        if(resultSet.moveToFirst()) {
+            int sequence = resultSet.getInt(0);
+            if(event.getSequenceId() <= sequence)
+                return;
+            isExisting = true;
+        }
+        resultSet.close();
+
+        if(isExisting) {
+            try {
+                ContentValues updateValues = new ContentValues();
+                updateValues.put("SequenceId", event.getSequenceId());
+                updateValues.put("EventFrom", event.getEventFrom());
+                updateValues.put("EventTo", event.getEventTo());
+                updateValues.put("EventTitle", event.getEventTitle());
+                updateValues.put("EventLocation", event.getEventLocation());
+                updateValues.put("EventStatus", event.getEventStatus());
+                updateValues.put("RecurRule", event.getRecurRule());
+                updateValues.put("ExcludeDates", event.getExcludeDates());
+                if(event.getCourseGroup() != null)
+                    updateValues.put("CourseGroupId", event.getCourseGroup().getGroupId());
+
+                database.update("CourseCalendar", updateValues, "CourseName = ? AND UniqueId = ?", whereArgs);
+                updateCourseCalendarEventTable(event, true);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        } else {
+            try {
+                ContentValues indexValues = new ContentValues();
+                indexValues.put("CourseName", event.getCourseName());
+                indexValues.put("UniqueId", event.getUniqueId());
+                indexValues.put("SequenceId", event.getSequenceId());
+                indexValues.put("EventFrom", event.getEventFrom());
+                indexValues.put("EventTo", event.getEventTo());
+                indexValues.put("EventTitle", event.getEventTitle());
+                indexValues.put("EventLocation", event.getEventLocation());
+                indexValues.put("EventStatus", event.getEventStatus());
+                indexValues.put("RecurRule", event.getRecurRule());
+                indexValues.put("ExcludeDates", event.getExcludeDates());
+                if(event.getCourseGroup() != null)
+                    indexValues.put("CourseGroupId", event.getCourseGroup().getGroupId());
+
+                long id = database.insertOrThrow("CourseCalendar", null, indexValues);
+                event.setEventId((int) id);
+                updateCourseCalendarEventTable(event, false);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+    }
+
+    private void updateCourseCalendarEventTable(CourseEvent event, boolean clear) {
+        if(clear) {
+            String[] whereArgs = {
+                    Integer.toString(event.getEventId())
+            };
+            database.rawQuery("DELETE FROM CourseCalendarEvent WHERE EventId = ?", whereArgs);
+        }
+
+        String rrule = event.getRecurRule();
+        String exdates = event.getExcludeDates();
+
+        if(rrule != null && rrule.length() > 0) {
+            try {
+                Log.i("DBM", "RRule: " + rrule);
+                RRule rule = new RRule(rrule);
+                Recur recur = rule.getRecur();
+                Date startDate = new Date((event.getEventFrom())*1000);
+                Date endDate = new Date(startDate.getTime() + (86400 * 365)*1000);
+
+                DateTime startDateTime = new DateTime(startDate);
+                DateList dates = recur.getDates(startDateTime, new Period(startDateTime, new DateTime(endDate)), Value.DATE);
+
+                Log.i("DBM", "Update events table for " + event.getUniqueId());
+                Log.i("DBM", "events: " +dates.size());
+
+                for(int i = 0; i < dates.size(); i++) {
+                    net.fortuna.ical4j.model.Date date = (net.fortuna.ical4j.model.Date) dates.get(i);
+                    long eventStartTime = date.getTime() / 1000;
+                    long eventEndTime = (eventStartTime + (event.getEventTo() - event.getEventFrom())) / 1000;
+
+                    try {
+                        ContentValues indexValues = new ContentValues();
+                        indexValues.put("EventId", event.getEventId());
+                        indexValues.put("EventFrom", eventStartTime);
+                        indexValues.put("EventTo", eventEndTime);
+                        database.insertOrThrow("CourseCalendarEvent", null, indexValues);
+                    } catch(Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+
+            } catch (ParseException e) {
+            }
+        } else {
+            try {
+                ContentValues indexValues = new ContentValues();
+                indexValues.put("EventId", event.getEventId());
+                indexValues.put("EventFrom", event.getEventFrom());
+                indexValues.put("EventTo", event.getEventTo());
+                database.insertOrThrow("CourseCalendarEvent", null, indexValues);
+            } catch(Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+    }
+
+    public CourseEvent[] getCourseCalendarEvents(String coursename, long timeFrom, long timeTo) {
+        String[] whereArgs = new String[] {
+                coursename,
+                Long.toString(timeFrom),
+                Long.toString(timeTo)
+        };
+        Cursor resultSet = database.rawQuery("SELECT Id, CourseName, UniqueId, SequenceId, EventFrom, EventTo, EventTitle, EventLocation, EventStatus, RecurRule, ExcludeDates, CourseGroupId FROM CourseCalendar WHERE CourseName = ? AND EventTo >= ? AND EventFrom <= ?", whereArgs);
+        ArrayList<CourseEvent> events = new ArrayList<CourseEvent>();
+        if(resultSet.moveToFirst()) {
+            int[] columnIndexes = {
+                    resultSet.getColumnIndex("Id"),
+                    resultSet.getColumnIndex("CourseName"),
+                    resultSet.getColumnIndex("UniqueId"),
+                    resultSet.getColumnIndex("SequenceId"),
+                    resultSet.getColumnIndex("EventFrom"),
+                    resultSet.getColumnIndex("EventTo"),
+                    resultSet.getColumnIndex("EventTitle"),
+                    resultSet.getColumnIndex("EventLocation"),
+                    resultSet.getColumnIndex("EventStatus"),
+                    resultSet.getColumnIndex("RecurRule"),
+                    resultSet.getColumnIndex("ExcludeDates"),
+                    resultSet.getColumnIndex("CourseGroupId")
+            };
+            do {
+                int groupId = resultSet.getInt(columnIndexes[11]);
+                CourseGroup group;
+                if(groupId > 0)
+                    group = CourseGroup.GetCourseGroupById(AppContext.getDatabaseManager(), groupId);
+                else
+                    group = null;
+                CourseEvent event = new CourseEvent(resultSet.getInt(columnIndexes[0]), resultSet.getString(columnIndexes[1]), resultSet.getString(columnIndexes[2]), resultSet.getInt(columnIndexes[3]),
+                        resultSet.getLong(columnIndexes[4]), resultSet.getLong(columnIndexes[5]), resultSet.getString(columnIndexes[6]), resultSet.getString(columnIndexes[7]),
+                        resultSet.getString(columnIndexes[8]), resultSet.getString(columnIndexes[9]), resultSet.getString(columnIndexes[10]), group);
+
+                events.add(event);
+            } while (resultSet.moveToNext());
+        }
+        resultSet.close();
+
+        CourseEvent[] eventsArr = new CourseEvent[events.size()];
+        eventsArr = events.toArray(eventsArr);
+        return eventsArr;
+    }
+
+    public CourseGroup getCourseGroup(int courseGroupId) {
+        CourseGroup coursegroup = null;
+        String[] whereArgs = new String[] {
+                Integer.toString(courseGroupId)
+        };
+        Cursor resultSet = database.rawQuery("SELECT CourseName, GroupName, LastUpdate FROM CourseCalendarGroup WHERE GroupId = ?", whereArgs);
+        if(resultSet.moveToFirst()) {
+            coursegroup = new CourseGroup(courseGroupId, resultSet.getString(0), resultSet.getString(1));
+        }
+        resultSet.close();
+        return coursegroup;
+    }
+
+    public CourseGroup getCourseGroup(String coursename, String groupname) {
+        CourseGroup coursegroup = null;
+        String[] whereArgs = new String[] {
+                coursename,
+                groupname
+        };
+        Cursor resultSet = database.rawQuery("SELECT GroupId, CourseName, GroupName, LastUpdate FROM CourseCalendarGroup WHERE CourseName = ? AND GroupName = ?", whereArgs);
+        if(resultSet.moveToFirst()) {
+            coursegroup = new CourseGroup(resultSet.getInt(0), resultSet.getString(1), resultSet.getString(2));
+        }
+        resultSet.close();
+        return coursegroup;
+    }
+
+    public CourseGroup addCourseGroup(String coursename, String groupname) {
+        long now = (new Date()).getTime() / 1000;
+        int id = 0;
+        try {
+            ContentValues indexValues = new ContentValues();
+            indexValues.put("CourseName",coursename);
+            indexValues.put("GroupName", groupname);
+            indexValues.put("LastUpdate", now);
+
+            id = (int) database.insertOrThrow("CourseCalendarGroup", null, indexValues);
+        } catch(Exception e) {
+            e.printStackTrace();
+        }
+
+        if(id > 0) {
+            CourseGroup newGroup = new CourseGroup(id, coursename, groupname);
+            return newGroup;
+        } else
+            return null;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearch.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearch.java
new file mode 100644 (file)
index 0000000..3ca9e02
--- /dev/null
@@ -0,0 +1,156 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.design.widget.TabLayout;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+import de.dhbwloe.campusapp.search.SearchTarget;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class AppSearch extends CampusAppFragment {
+    private ViewPagerAdapter oAppSearchLViewPagerAdapter;
+    private String sSearchQuery;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i("AppSearch", "Event: onCreate");
+        super.onCreate(savedInstanceState);
+
+        Bundle bundle;
+        if((bundle = getArguments()) != null)
+            sSearchQuery = bundle.getString("query");
+        else if(savedInstanceState != null && (bundle = savedInstanceState.getBundle("AppSearch")) != null)
+            sSearchQuery = bundle.getString("query");
+
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        Log.i("AppSearch", "Event: onSaveInstanceState");
+        Bundle bundle = new Bundle();
+        bundle.putString("query", sSearchQuery);
+        savedInstanceState.putBundle("AppSearch", bundle);
+        super.onSaveInstanceState(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        Log.i("AppSearch", "Event: onCreateView");
+        if(AppContext == null)
+            AppContext = CampusAppContext.getInstance();
+        oFragmentView = inflater.inflate(R.layout.fragment_appsearch, container, false);
+
+        AppContext.setTitle("Search: " + sSearchQuery);
+
+        ViewPager viewPager = (ViewPager) oFragmentView.findViewById(R.id.viewpager);
+        setupViewPager(viewPager);
+
+        TabLayout tabLayout = (TabLayout) oFragmentView.findViewById(R.id.tabs);
+        tabLayout.setupWithViewPager(viewPager);
+
+        return oFragmentView;
+    }
+
+    private void setupViewPager(ViewPager viewPager) {
+        final ViewPagerAdapter adapter;
+        if(oAppSearchLViewPagerAdapter != null)
+            adapter = oAppSearchLViewPagerAdapter;
+        else {
+            adapter = new ViewPagerAdapter(getChildFragmentManager());
+            oAppSearchLViewPagerAdapter = adapter;
+
+            CampusAppFragment fragment;
+            Bundle args = new Bundle();
+            args.putString("query", sSearchQuery);
+            Log.i("AppSearch", "New result instances: "+sSearchQuery);
+
+            fragment = new AppSearchInternal();
+            fragment.setArguments(args);
+            adapter.addFragment("Campus App", fragment);
+
+            fragment = new AppSearchDhbw();
+            fragment.setArguments(args);
+            adapter.addFragment("DHBW Suche", fragment);
+
+            fragment = new AppSearchStuv();
+            fragment.setArguments(args);
+            adapter.addFragment("Stuv Suche", fragment);
+        }
+
+        viewPager.setAdapter(adapter);
+        int activeItem = viewPager.getCurrentItem();
+        AppSearchProvider activeProvider = (AppSearchProvider) adapter.getItem(activeItem);
+        activeProvider.executeSearch(false);
+
+        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+            @Override
+            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+
+            }
+
+            @Override
+            public void onPageSelected(int position) {
+                AppSearchProvider activeProvider = (AppSearchProvider) adapter.getItem(position);
+                activeProvider.executeSearch(false);
+            }
+
+            @Override
+            public void onPageScrollStateChanged(int state) {
+
+            }
+        });
+    }
+
+
+    class ViewPagerAdapter extends FragmentPagerAdapter {
+        private final List<Fragment> mFragmentDataList = new ArrayList<>();
+        private final List<String> mFragmentTitleList = new ArrayList<>();
+
+        public ViewPagerAdapter(FragmentManager manager) {
+            super(manager);
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            return mFragmentDataList.get(position);
+        }
+
+        @Override
+        public int getCount() {
+            return mFragmentDataList.size();
+        }
+
+        public void addFragment(String title, Fragment fragment) {
+            mFragmentDataList.add(fragment);
+            mFragmentTitleList.add(title);
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            return mFragmentTitleList.get(position);
+        }
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchDhbw.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchDhbw.java
new file mode 100644 (file)
index 0000000..3159aca
--- /dev/null
@@ -0,0 +1,129 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.DhbwSearchHelper;
+import de.dhbwloe.campusapp.search.SearchIndices;
+import de.dhbwloe.campusapp.search.SearchResultListener;
+import de.dhbwloe.campusapp.search.SearchTarget;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class AppSearchDhbw extends CampusAppFragment implements AppSearchProvider {
+    private String sSearchQuery;
+    private boolean bQueryExecuted = false;
+    private AppSearchListAdapter appSearchAdapter;
+    private DhbwSearchHelper seachHelper;
+    private ArrayList<AppSearchListItem> searchResultItems = new ArrayList<AppSearchListItem>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i("AppSearchDhbw", "Event: onCreate");
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        Log.i("AppSearchDhbw", "Event: onSaveInstanceState");
+        Bundle bundle = new Bundle();
+        bundle.putString("query", sSearchQuery);
+        bundle.putBoolean("executed", bQueryExecuted);
+        savedInstanceState.putBundle("SearchDhbw", bundle);
+        super.onSaveInstanceState(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        Log.i("AppSearchDhbw", "Event: onCreateView");
+        Bundle savedState;
+        if((savedState = getArguments()) != null)
+            sSearchQuery = savedState.getString("query");
+        else if(savedInstanceState != null && (savedState = savedInstanceState.getBundle("SearchDhbw")) != null) {
+            Log.i("AppSearchDhbw", "Parsed instance state");
+            sSearchQuery = savedState.getString("query");
+            if(savedState.getBoolean("executed")) {
+                bQueryExecuted = true;
+            }
+        }
+        if(AppContext == null)
+            AppContext = CampusAppContext.getInstance();
+        oFragmentView = inflater.inflate(R.layout.fragment_appsearch_list, container, false);
+
+        Bundle args = getArguments();
+        if(args != null) {
+            String queryString = args.getString("query");
+            if(queryString != null)
+                sSearchQuery = queryString;
+        }
+
+        ListView searchResultList = (ListView) oFragmentView.findViewById(R.id.searchResultItems);
+        appSearchAdapter = new AppSearchListAdapter(oFragmentView.getContext(), R.layout.fragment_appsearch_listitem, searchResultItems);
+        searchResultList.setAdapter(appSearchAdapter);
+
+        searchResultList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                AppSearchListItem item = searchResultItems.get(position);
+                if (item == null)
+                    return;
+                navigateToResult(item);
+            }
+        });
+
+        return oFragmentView;
+    }
+
+    public void executeSearch(boolean reset) {
+        Log.i("AppSearchDhbw", "Action: executeSearch");
+        if(AppContext == null)
+            AppContext = CampusAppContext.getInstance();
+        if(bQueryExecuted && !reset)
+            return;
+        bQueryExecuted = true;
+
+        searchResultItems.clear();
+
+        if(seachHelper == null)
+            seachHelper = new DhbwSearchHelper();
+        seachHelper.search(sSearchQuery, new SearchResultListener() {
+            @Override
+            public void onSearchResultsReceived(List<AppSearchListItem> results) {
+                Log.i("SearchDhbw", "Success: "+results.size());
+                for(AppSearchListItem result : results)
+                    searchResultItems.add(result);
+                if(appSearchAdapter != null)
+                    appSearchAdapter.notifyDataSetChanged();
+                else
+                    Log.i("SearchDhbw", "appSearchAdapter is null in executeSearch");
+            }
+
+            @Override
+            public void onSearchFailed(String error) {
+                Log.i("SearchDhbw", "Failed: " + error);
+            }
+        });
+    }
+
+    private void navigateToResult(AppSearchListItem result) {
+        SearchTarget target = result.getTarget();
+
+        target.navigate(AppContext.getNavigationManager());
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchInternal.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchInternal.java
new file mode 100644 (file)
index 0000000..2c10dbb
--- /dev/null
@@ -0,0 +1,129 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+import de.dhbwloe.campusapp.search.SearchTarget;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class AppSearchInternal extends CampusAppFragment implements AppSearchProvider {
+    private String sSearchQuery;
+    private boolean bQueryExecuted = false;
+    private AppSearchListAdapter appSearchAdapter;
+    private ArrayList<AppSearchListItem> searchResultItems = new ArrayList<AppSearchListItem>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i("AppSearchInternal", "Event: onCreate");
+        super.onCreate(savedInstanceState);
+        Bundle savedState;
+        if((savedState = getArguments()) != null)
+            sSearchQuery = savedState.getString("query");
+        else if(savedInstanceState != null && (savedState = savedInstanceState.getBundle("SearchInternal")) != null) {
+            sSearchQuery = savedState.getString("query");
+            if(savedState.getBoolean("executed")) {
+                executeSearch(false);
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        Log.i("AppSearchInternal", "Event: onSaveInstanceState");
+        Bundle bundle = new Bundle();
+        bundle.putString("query", sSearchQuery);
+        bundle.putBoolean("executed", bQueryExecuted);
+        savedInstanceState.putBundle("SearchInternal", bundle);
+        super.onSaveInstanceState(savedInstanceState);
+    }
+
+    private void parseIstanceState(Bundle bundle) {
+        if(sSearchQuery != null)
+            return;
+        Bundle args = getArguments();
+        if(args != null) {
+            String queryString = args.getString("query");
+            if(queryString != null)
+                sSearchQuery = queryString;
+        }
+        if(sSearchQuery == null && bundle != null) {
+            String queryString = bundle.getString("query");
+            if(queryString != null)
+                sSearchQuery = queryString;
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        Log.i("AppSearchInternal", "Event: onCreateView");
+        if(AppContext == null)
+            AppContext = CampusAppContext.getInstance();
+        oFragmentView = inflater.inflate(R.layout.fragment_appsearch_list, container, false);
+
+        parseIstanceState(savedInstanceState);
+
+        ListView searchResultList = (ListView) oFragmentView.findViewById(R.id.searchResultItems);
+        appSearchAdapter = new AppSearchListAdapter(oFragmentView.getContext(), R.layout.fragment_appsearch_listitem, searchResultItems);
+        searchResultList.setAdapter(appSearchAdapter);
+
+        searchResultList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                AppSearchListItem item = searchResultItems.get(position);
+                if (item == null)
+                    return;
+                navigateToResult(item);
+            }
+        });
+
+        return oFragmentView;
+    }
+
+    public void executeSearch(boolean reset) {
+        if(sSearchQuery == null)
+            parseIstanceState(null);
+        Log.i("AppSearchInternal", "Action: executeSearch "+sSearchQuery);
+        if(AppContext == null)
+            AppContext = CampusAppContext.getInstance();
+        if(bQueryExecuted && !reset)
+            return;
+        bQueryExecuted = true;
+
+        searchResultItems.clear();
+
+        SearchIndices[] indices = AppContext.getDatabaseManager().performSearchRequest(sSearchQuery, 40);
+        for(int i = 0; i < indices.length; i++) {
+            SearchTarget target = new SearchTarget(indices[i].getTarget());
+            AppSearchListItem item = new AppSearchListItem(indices[i].getTitle(), indices[i].getDescription(), target);
+            searchResultItems.add(item);
+        }
+        if(appSearchAdapter != null)
+            appSearchAdapter.notifyDataSetChanged();
+
+    }
+
+    private void navigateToResult(AppSearchListItem result) {
+        SearchTarget target = result.getTarget();
+
+        target.navigate(AppContext.getNavigationManager());
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchListAdapter.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchListAdapter.java
new file mode 100644 (file)
index 0000000..90015d4
--- /dev/null
@@ -0,0 +1,62 @@
+package de.dhbwloe.campusapp.fragments;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import de.dhbwloe.campusapp.R;
+
+/**
+ * Created by pk910 on 19.01.2016.
+ */
+public class AppSearchListAdapter  extends ArrayAdapter<AppSearchListItem> {
+    private Context context;
+    private int layoutResourceId;
+    private ArrayList<AppSearchListItem> data = new ArrayList<AppSearchListItem>();
+
+    public AppSearchListAdapter(Context context, int layoutResourceId, ArrayList<AppSearchListItem> data) {
+        super(context, layoutResourceId, data);
+        this.layoutResourceId = layoutResourceId;
+        this.context = context;
+        this.data = data;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View row = convertView;
+        RecordHolder holder = null;
+
+        if (row == null) {
+            LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            row = inflater.inflate(layoutResourceId, parent, false);
+
+            holder = new RecordHolder(row);
+            row.setTag(holder);
+        } else {
+            holder = (RecordHolder) row.getTag();
+        }
+
+        final AppSearchListAdapter that = this;
+        final AppSearchListItem item = data.get(position);
+
+        holder.resultTitle.setText(item.getTitle());
+        holder.resultDescription.setText(item.getDescription());
+
+        return row;
+    }
+
+    static class RecordHolder {
+        TextView resultTitle;
+        TextView resultDescription;
+
+        public RecordHolder(View view) {
+            this.resultTitle = (TextView) view.findViewById(R.id.resultTitle);
+            this.resultDescription = (TextView) view.findViewById(R.id.resultDescription);
+        }
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchListItem.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchListItem.java
new file mode 100644 (file)
index 0000000..cff66a1
--- /dev/null
@@ -0,0 +1,30 @@
+package de.dhbwloe.campusapp.fragments;
+
+import de.dhbwloe.campusapp.search.SearchTarget;
+
+/**
+ * Created by pk910 on 19.01.2016.
+ */
+public class AppSearchListItem {
+    private String title;
+    private String description;
+    private SearchTarget target;
+
+    public AppSearchListItem(String title, String description, SearchTarget target) {
+        this.title = title;
+        this.description = description;
+        this.target = target;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public SearchTarget getTarget() {
+        return target;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchProvider.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchProvider.java
new file mode 100644 (file)
index 0000000..550a2b5
--- /dev/null
@@ -0,0 +1,8 @@
+package de.dhbwloe.campusapp.fragments;
+
+/**
+ * Created by pk910 on 25.01.2016.
+ */
+public interface AppSearchProvider {
+    public void executeSearch(boolean reset);
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchStuv.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/AppSearchStuv.java
new file mode 100644 (file)
index 0000000..3aad66b
--- /dev/null
@@ -0,0 +1,126 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.DhbwSearchHelper;
+import de.dhbwloe.campusapp.search.SearchIndices;
+import de.dhbwloe.campusapp.search.SearchResultListener;
+import de.dhbwloe.campusapp.search.SearchTarget;
+import de.dhbwloe.campusapp.search.StuvSearchHelper;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class AppSearchStuv extends CampusAppFragment implements AppSearchProvider {
+    private String sSearchQuery;
+    private boolean bQueryExecuted = false;
+    private boolean bExecuteOnload = false;
+    private AppSearchListAdapter appSearchAdapter;
+    private StuvSearchHelper seachHelper;
+    private ArrayList<AppSearchListItem> searchResultItems = new ArrayList<AppSearchListItem>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Bundle savedState;
+        if((savedState = getArguments()) != null)
+            sSearchQuery = savedState.getString("query");
+        if(savedInstanceState != null && (savedState = savedInstanceState.getBundle("SearchStuv")) != null) {
+            sSearchQuery = savedState.getString("query");
+            if(savedState.getBoolean("executed")) {
+                executeSearch(false);
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        Bundle bundle = new Bundle();
+        bundle.putString("query", sSearchQuery);
+        bundle.putBoolean("executed", bQueryExecuted);
+        savedInstanceState.putBundle("SearchStuv", bundle);
+        super.onSaveInstanceState(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        if(AppContext == null)
+            AppContext = CampusAppContext.getInstance();
+        oFragmentView = inflater.inflate(R.layout.fragment_appsearch_list, container, false);
+
+        Bundle args = getArguments();
+        if(args != null) {
+            String queryString = args.getString("query");
+            if(queryString != null)
+                sSearchQuery = queryString;
+        }
+
+        ListView searchResultList = (ListView) oFragmentView.findViewById(R.id.searchResultItems);
+        if(appSearchAdapter == null)
+            appSearchAdapter = new AppSearchListAdapter(oFragmentView.getContext(), R.layout.fragment_appsearch_listitem, searchResultItems);
+        searchResultList.setAdapter(appSearchAdapter);
+
+        searchResultList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                AppSearchListItem item = searchResultItems.get(position);
+                if (item == null)
+                    return;
+                navigateToResult(item);
+            }
+        });
+
+        return oFragmentView;
+    }
+
+    public void executeSearch(final boolean reset) {
+        if(AppContext == null)
+            AppContext = CampusAppContext.getInstance();
+        if(bQueryExecuted && !reset)
+            return;
+        bQueryExecuted = true;
+
+        searchResultItems.clear();
+
+        if(seachHelper == null)
+            seachHelper = new StuvSearchHelper();
+        seachHelper.search(sSearchQuery, new SearchResultListener() {
+            @Override
+            public void onSearchResultsReceived(List<AppSearchListItem> results) {
+                Log.i("SearchStuv", "Success: "+results.size());
+                for(AppSearchListItem result : results)
+                    searchResultItems.add(result);
+                if(appSearchAdapter != null)
+                    appSearchAdapter.notifyDataSetChanged();
+            }
+
+            @Override
+            public void onSearchFailed(String error) {
+                Log.i("SearchDhbw", "Failed: " + error);
+            }
+        });
+
+    }
+
+    private void navigateToResult(AppSearchListItem result) {
+        SearchTarget target = result.getTarget();
+
+        target.navigate(AppContext.getNavigationManager());
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/Dashboard.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/Dashboard.java
new file mode 100644 (file)
index 0000000..5bea0bc
--- /dev/null
@@ -0,0 +1,43 @@
+package de.dhbwloe.campusapp.fragments;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+public class Dashboard extends CampusAppFragment {
+    /* implement this for search results ;) */
+    public static SearchIndices[] GetSearchIndices() {
+        return new SearchIndices[] {
+                new SearchIndices("Dashboard", true) {{
+                    setUpdateTime(1);
+                    setTarget("#Dashboard");
+                    setTitle("Dashboard");
+                    setDescription("Dashboard der App :)");
+                    addKeyWord("home, dashboard, start, übersicht, overview");
+                }},
+        };
+    }
+
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_dashboard, container, false);
+        AppContext.setTitle("Dashboard");
+
+
+
+        return view;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/FirstRun.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/FirstRun.java
new file mode 100644 (file)
index 0000000..b84a9ab
--- /dev/null
@@ -0,0 +1,37 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class FirstRun extends CampusAppFragment {
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_first_run, container, false);
+        AppContext.setTitle("Campus App");
+
+        Button startAppBtn = (Button)view.findViewById(R.id.startAppBtn);
+        startAppBtn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AppContext.getDatabaseManager().setRuntimeCache("AppStartCounter", "1");
+                AppContext.getNavigationManager().navigatePage("SplashScreen", null, false);
+            }
+        });
+
+        return view;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/Impressum.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/Impressum.java
new file mode 100644 (file)
index 0000000..a888f2a
--- /dev/null
@@ -0,0 +1,40 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class Impressum extends CampusAppFragment {
+    /* implement this for search results ;) */
+    public static SearchIndices[] GetSearchIndices() {
+        return new SearchIndices[] {
+                new SearchIndices("Impressum", true) {{
+                    setUpdateTime(1);
+                    setTarget("#Impressum");
+                    setTitle("Impressum");
+                    setDescription("Impressum der App");
+                    addKeyWord("impressum, kontakt, autor, author, contact, imprint");
+                }},
+        };
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_impressum, container, false);
+        AppContext.setTitle("Impressum");
+
+        return view;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/Mensa.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/Mensa.java
new file mode 100644 (file)
index 0000000..ccc7785
--- /dev/null
@@ -0,0 +1,213 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.design.widget.TabLayout;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class Mensa extends CampusAppFragment {
+    /* implement this for search results ;) */
+    public static SearchIndices[] GetSearchIndices() {
+        return new SearchIndices[] {
+                new SearchIndices("Mensa", true) {{
+                    setUpdateTime(1);
+                    setTarget("#Mensa");
+                    setTitle("Mensa");
+                    setDescription("Aktuelle Mensapläne");
+                    addKeyWord("mensa, kantine, essen, mittagessen, mensaplan, plan, hunger, mittag");
+                }},
+        };
+    }
+
+    private TabLayout tabLayout;
+    private ViewPager viewPager;
+    private boolean viewDayplan;
+    private long viewDate;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_mensa, container, false);
+        boolean viewmodeSet = false;
+        Bundle arguments = getArguments();
+        viewDate = (new Date()).getTime()/1000;
+        if(arguments != null) {
+            String showdate = arguments.getString("showdate");
+            long date;
+            if(showdate != null && !showdate.isEmpty() && (date = Long.parseLong(showdate)) > 0) {
+                viewDate = date;
+                viewDayplan = true;
+                viewmodeSet = true;
+            }
+            if(!viewmodeSet) {
+                String viewmode = arguments.getString("viewmode");
+                if(viewmode != null && !viewmode.isEmpty()) {
+                    if(viewmode.equalsIgnoreCase("day"))
+                        viewDayplan = true;
+                    else
+                        viewDayplan = false;
+                    viewmodeSet = true;
+                }
+            }
+        }
+        if(!viewmodeSet) {
+            String lastViewmode = AppContext.getDatabaseManager().getRuntimeCache("MensaplanViewMode");
+            if(lastViewmode != null && !lastViewmode.isEmpty()) {
+                if(lastViewmode.equalsIgnoreCase("day"))
+                    viewDayplan = true;
+                else
+                    viewDayplan = false;
+                viewmodeSet = true;
+            }
+        }
+        if(!viewmodeSet)
+            viewDayplan = true;
+
+        AppContext.setTitle("Mensa "+(viewDayplan ? "Tagesplan" : "Wochenplan"));
+
+        /*
+        toolbar = (Toolbar) view.findViewById(R.id.toolbar);
+        ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
+
+        ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        */
+
+        viewPager = (ViewPager) view.findViewById(R.id.viewpager);
+        setupViewPager(viewPager);
+
+        tabLayout = (TabLayout) view.findViewById(R.id.tabs);
+        tabLayout.setupWithViewPager(viewPager);
+
+        return view;
+    }
+
+    private void setupViewPager(ViewPager viewPager) {
+        final ViewPagerAdapter adapter = new ViewPagerAdapter(getChildFragmentManager());
+
+        long[] showDates;
+        long now = (new Date()).getTime()/1000;
+        if(viewDayplan)
+            showDates = AppContext.getDatabaseManager().getDaysWithPlanData(now - (30 * 86400), now + (30 * 86400));
+        else
+            showDates = AppContext.getDatabaseManager().getDaysWithPlanData(now - (365 * 86400), now + (30 * 86400));
+
+        int activeItem = -1;
+        long activeItemDiff = -1;
+        Bundle args = getArguments();
+        for(int i = 0; i < showDates.length; i++) {
+            long date = showDates[i];
+            Bundle bundle = new Bundle();
+            if(args != null)
+                bundle.putAll(args);
+            String title;
+            SimpleDateFormat sdf;
+            if(viewDayplan)
+                sdf = new SimpleDateFormat("dd.MM.");
+            else
+                sdf = new SimpleDateFormat("WW");
+
+            if(viewDate > 0) {
+                long diff = Math.abs(viewDate - date);
+                if(activeItem == -1 || diff < activeItemDiff) {
+                    activeItem = i;
+                    activeItemDiff = diff;
+                }
+            }
+
+            title = sdf.format(new Date(date * 1000));
+            bundle.putLong("date", date);
+            adapter.addFragment(bundle, title);
+        }
+        viewPager.setAdapter(adapter);
+        viewPager.setCurrentItem(activeItem);
+        if(viewDayplan) {
+            MensaTagesplan tagesplan = (MensaTagesplan)adapter.getItem(activeItem);
+            tagesplan.onSetActive();
+        }
+
+        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+            @Override
+            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+
+            }
+
+            @Override
+            public void onPageSelected(int position) {
+                if(viewDayplan) {
+                    MensaTagesplan tagesplan = (MensaTagesplan)adapter.getItem(position);
+                    tagesplan.onSetActive();
+                }
+            }
+
+            @Override
+            public void onPageScrollStateChanged(int state) {
+
+            }
+        });
+    }
+
+
+    class ViewPagerAdapter extends FragmentPagerAdapter {
+        private final List<Fragment> mFragmentList = new ArrayList<>();
+        private final List<Bundle> mFragmentDataList = new ArrayList<>();
+        private final List<String> mFragmentTitleList = new ArrayList<>();
+
+        public ViewPagerAdapter(FragmentManager manager) {
+            super(manager);
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            Fragment fragment;
+            while(mFragmentList.size() <= position) {
+                mFragmentList.add(null);
+            }
+            if(mFragmentList.get(position) != null)
+                return mFragmentList.get(position);
+            if(viewDayplan)
+                fragment = new MensaTagesplan();
+            else
+                fragment = new MensaWochenplan();
+            fragment.setArguments(mFragmentDataList.get(position));
+            mFragmentList.set(position, fragment);
+            return fragment;
+        }
+
+        @Override
+        public int getCount() {
+            return mFragmentDataList.size();
+        }
+
+        public void addFragment(Bundle fragmentdata, String title) {
+            fragmentdata.putBoolean("parentIsMensaFragment", true);
+            mFragmentDataList.add(fragmentdata);
+            mFragmentTitleList.add(title);
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            return mFragmentTitleList.get(position);
+        }
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/MensaCard.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/MensaCard.java
new file mode 100644 (file)
index 0000000..e465d72
--- /dev/null
@@ -0,0 +1,63 @@
+package de.dhbwloe.campusapp.fragments;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.text.DecimalFormat;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Activities that contain this fragment must implement the
+ * {@link MensaCard.OnFragmentInteractionListener} interface
+ * to handle interaction events.
+ * Use the {@link MensaCard#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class MensaCard extends CampusAppFragment {
+    private View view;
+
+    /* implement this for search results ;) */
+    public static SearchIndices[] GetSearchIndices() {
+        return new SearchIndices[] {
+                new SearchIndices("MensaCard", true) {{
+                    setUpdateTime(1);
+                    setTarget("#MensaCard");
+                    setTitle("Mensakarte auslesen");
+                    setDescription("Mensakarte Kontostand");
+                    addKeyWord("mensa, kantine, essen, mittagessen, mensaplan, karte, ausweis, geld, kontostand, euro");
+                }},
+        };
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        view = inflater.inflate(R.layout.fragment_mensa_card, container, false);
+        AppContext.setTitle("Mensa Guthaben");
+
+        Bundle args = getArguments();
+        if(args != null && args.containsKey("balance")) {
+            showNfcCardData(args);
+        }
+
+        return view;
+    }
+
+    public void showNfcCardData(Bundle bundle) {
+        TextView cardDataView = (TextView)view.findViewById(R.id.balanceTxt);
+        double balance = bundle.getDouble("balance");
+        DecimalFormat df = new DecimalFormat("#,###.00");
+        cardDataView.setText(df.format(balance)+" €");
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/MensaTagesplan.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/MensaTagesplan.java
new file mode 100644 (file)
index 0000000..1d1022a
--- /dev/null
@@ -0,0 +1,113 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class MensaTagesplan extends CampusAppFragment {
+    private ArrayList<de.dhbwloe.campusapp.mensaplan.MensaTagesplan> tagesplanMenueItems = new ArrayList<>();
+    private Date viewPlanDate;
+    private boolean setActiveOnLoad = false;
+
+    public void onSetActive() {
+        if(viewPlanDate == null) {
+            setActiveOnLoad = true;
+            return;
+        }
+        SimpleDateFormat titleDateFormater = new SimpleDateFormat("dd.MM.yyyy");
+        AppContext.setTitle("Mensa: " + titleDateFormater.format(viewPlanDate));
+    }
+
+    private void setupPlanDate() {
+        Bundle args = getArguments();
+        Date planDay = null;
+        if(args != null) {
+            long plandate = args.getLong("date");
+            if(plandate > 0) {
+                planDay = new Date(plandate * 1000);
+                DateFormat justDay = new SimpleDateFormat("yyyyMMdd");
+                try {
+                    planDay = justDay.parse(justDay.format(planDay));
+                } catch (Exception e) {
+                }
+            }
+        }
+        if(planDay == null) {
+            return;
+        }
+        viewPlanDate = planDay;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setupPlanDate();
+
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_mensa_tagesplan, container, false);
+
+        if(viewPlanDate == null) {
+            AppContext.getNavigationManager().navigatePage("Mensa", null, false); // silent redirect
+            return null;
+        }
+        if(setActiveOnLoad) {
+            setActiveOnLoad = false;
+            onSetActive();
+        }
+
+        ListView tagesplanItemsList = (ListView) view.findViewById(R.id.tagesplanItems);
+        MensaTagesplanListAdapter listAdapter = new MensaTagesplanListAdapter(view.getContext(), R.layout.fragment_mensa_tagesplan_listitem, tagesplanMenueItems);
+        tagesplanItemsList.setAdapter(listAdapter);
+
+        tagesplanItemsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                de.dhbwloe.campusapp.mensaplan.MensaTagesplan item = tagesplanMenueItems.get(position);
+                if (item == null)
+                    return;
+
+                // do sth?
+            }
+        });
+
+        requestMenues();
+
+        return view;
+    }
+
+    private void requestMenues() {
+        Date endOfDay = (Date)viewPlanDate.clone();
+        endOfDay.setTime(endOfDay.getTime() + (86400 - 1) * 1000);
+
+        de.dhbwloe.campusapp.mensaplan.MensaTagesplan menues[] = AppContext.getDatabaseManager().getMensaTagesplan(viewPlanDate.getTime()/1000, endOfDay.getTime()/1000);
+        tagesplanMenueItems.clear();
+        for(de.dhbwloe.campusapp.mensaplan.MensaTagesplan menue : menues) {
+            tagesplanMenueItems.add(menue);
+        }
+
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/MensaTagesplanListAdapter.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/MensaTagesplanListAdapter.java
new file mode 100644 (file)
index 0000000..f0bf2a1
--- /dev/null
@@ -0,0 +1,66 @@
+package de.dhbwloe.campusapp.fragments;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.mensaplan.*;
+
+/**
+ * Created by pk910 on 24.01.2016.
+ */
+
+public class MensaTagesplanListAdapter  extends ArrayAdapter<de.dhbwloe.campusapp.mensaplan.MensaTagesplan> {
+    private Context context;
+    private int layoutResourceId;
+    private ArrayList<de.dhbwloe.campusapp.mensaplan.MensaTagesplan> data = new ArrayList<de.dhbwloe.campusapp.mensaplan.MensaTagesplan>();
+
+    public MensaTagesplanListAdapter(Context context, int layoutResourceId, ArrayList<de.dhbwloe.campusapp.mensaplan.MensaTagesplan> data) {
+        super(context, layoutResourceId, data);
+        this.layoutResourceId = layoutResourceId;
+        this.context = context;
+        this.data = data;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View row = convertView;
+        RecordHolder holder = null;
+
+        if (row == null) {
+            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            row = inflater.inflate(layoutResourceId, parent, false);
+
+            holder = new RecordHolder(row);
+            row.setTag(holder);
+        } else {
+            holder = (RecordHolder) row.getTag();
+        }
+
+        final MensaTagesplanListAdapter that = this;
+        final de.dhbwloe.campusapp.mensaplan.MensaTagesplan item = data.get(position);
+
+        if(holder.txtMenueName != null)
+            holder.txtMenueName.setText(item.getMenuName());
+        if(holder.txtName != null)
+            holder.txtName.setText(item.getName());
+
+        return row;
+    }
+
+    static class RecordHolder {
+        TextView txtMenueName;
+        TextView txtName;
+
+        public RecordHolder(View view) {
+            this.txtMenueName = (TextView) view.findViewById(R.id.txtMenueName);
+            this.txtName = (TextView) view.findViewById(R.id.txtName);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/MensaWochenplan.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/MensaWochenplan.java
new file mode 100644 (file)
index 0000000..35917f0
--- /dev/null
@@ -0,0 +1,31 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class MensaWochenplan extends CampusAppFragment {
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_mensa_wochenplan, container, false);
+        AppContext.setTitle("Mensa Wochenplan: ");
+
+
+
+        return view;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/News.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/News.java
new file mode 100644 (file)
index 0000000..6a48b68
--- /dev/null
@@ -0,0 +1,42 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class News extends CampusAppFragment {
+    /* implement this for search results ;) */
+    public static SearchIndices[] GetSearchIndices() {
+        return new SearchIndices[] {
+                new SearchIndices("News", true) {{
+                    setUpdateTime(1);
+                    setTarget("#News");
+                    setTitle("News");
+                    setDescription("News der DHBW & Stuv");
+                    addKeyWord("news, dhbw, stuv, termin, termine");
+                }},
+        };
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_news, container, false);
+        AppContext.setTitle("News");
+
+
+        return view;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/PopupFragment.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/PopupFragment.java
new file mode 100644 (file)
index 0000000..69f36d5
--- /dev/null
@@ -0,0 +1,80 @@
+package de.dhbwloe.campusapp.fragments;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+import de.dhbwloe.campusapp.search.SearchTarget;
+
+/**
+ * Created by pk910 on 21.01.2016.
+ */
+public class PopupFragment extends DialogFragment {
+    private CampusAppContext AppContext;
+    private CampusAppFragment oCurrentFragment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        AppContext = CampusAppContext.getInstance();
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.content_popup, container, false);
+        getDialog().setOnCancelListener(new DialogInterface.OnCancelListener() {
+            @Override
+            public void onCancel(DialogInterface dialog) {
+                AppContext.getNavigationManager().closeDialog();
+            }
+        });
+
+        Bundle args = getArguments();
+        if(args != null) {
+            String targetPage = args.getString("target");
+            SearchTarget target = new SearchTarget(targetPage);
+            String targetPageName;
+            if(target.isInAppTarget())
+                targetPageName = target.getTargetUrl();
+            else {
+                targetPageName = "WebBrowser";
+                args.putString("url", target.getTargetUrl());
+            }
+            CampusAppFragment fragment = AppContext.getNavigationManager().getPageFragment(targetPageName);
+            fragment.setArguments(args);
+
+            FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+            transaction.add(R.id.content_container, fragment);
+            oCurrentFragment = fragment;
+            transaction.commit();
+        }
+
+        return view;
+    }
+
+    public void destroyView() {
+        if(oCurrentFragment != null) {
+            FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+            transaction.remove(oCurrentFragment);
+            transaction.commit();
+        }
+    }
+
+    public CampusAppFragment getCurrentFragment() {
+        return oCurrentFragment;
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/SplashScreen.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/SplashScreen.java
new file mode 100644 (file)
index 0000000..eb543d5
--- /dev/null
@@ -0,0 +1,254 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.Fragment;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import java.util.Date;
+
+import de.dhbwloe.campusapp.CampusApp;
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.mensaplan.MensaplanManager;
+import de.dhbwloe.campusapp.mensaplan.MensaplanManagerInterface;
+import de.dhbwloe.campusapp.news.NewsManager;
+import de.dhbwloe.campusapp.news.NewsManagerInterface;
+import de.dhbwloe.campusapp.vorlesungen.VorlesungsplanManager;
+import de.dhbwloe.campusapp.vorlesungen.VorlesungsplanManagerInterface;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class SplashScreen extends CampusAppFragment {
+    private ProgressBar splashProgress;
+    private int progressCounter;
+    private Handler timerHandler = new Handler();
+    private Runnable timerRunnable;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        timerHandler.removeCallbacksAndMessages(null);
+        super.onSaveInstanceState(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_splashscreen, container, false);
+
+        splashProgress = (ProgressBar)view.findViewById(R.id.splashProgress);
+        splashProgress.setMax(20);
+        splashProgress.setProgress(0);
+        progressCounter = 0;
+
+        AppContext.setTitle("DHBW Lörrach");
+
+        ImageView splashImage = (ImageView)view.findViewById(R.id.splashImage);
+        BitmapFactory.Options dimensions = new BitmapFactory.Options();
+        Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dhbw_campus_hd, dimensions);
+        int height = dimensions.outHeight;
+        int width =  dimensions.outWidth;
+        Display display = AppContext.getMainActivity().getWindowManager().getDefaultDisplay();
+        Point size = new Point();
+        display.getSize(size);
+        float scaleX = (float)size.x / (float)width;
+        float scaleY = (float)size.y / (float)height;
+        float scale = Math.max(scaleX, scaleY);
+        int newWidth = (int)(width*scale);
+        int newHeight = (int)(height*scale);
+        Bitmap newBitmap = Bitmap.createScaledBitmap(mBitmap, newWidth, newHeight, true);
+        splashImage.setImageBitmap(newBitmap);
+
+        timerRunnable = new Runnable() {
+            @Override
+            public void run() {
+                progressCounter++;
+                splashProgress.setProgress(progressCounter);
+                long now = (new Date()).getTime()/1000;
+                switch(progressCounter) {
+                    case 1:
+                        AppContext.getDatabaseManager().initializeDatabase();
+                        break;
+                    case 2:
+                        AppContext.addDefaultSearchIndexes();
+                        break;
+                    case 3:
+                        String startCounter = AppContext.getDatabaseManager().getRuntimeCache("AppStartCounter");
+                        if(startCounter == null || Integer.parseInt(startCounter) == 0) {
+                            AppContext.getNavigationManager().navigatePage("FirstRun", null, false);
+                            return;
+                        }
+                        AppContext.getDatabaseManager().setRuntimeCache("AppStartCounter", Integer.toString(Integer.parseInt(startCounter) + 1));
+                        break;
+                    case 4:
+                        String lastVLMFullSyncStr = AppContext.getDatabaseManager().getRuntimeCache("LastVLMFullSync");
+                        String lastVLMPartialSyncStr = AppContext.getDatabaseManager().getRuntimeCache("LastVLMPartialSync");
+                        long lastVLMFullSync, lastVLMPartialSync;
+                        if(lastVLMFullSyncStr != null)
+                            lastVLMFullSync = Long.parseLong(lastVLMFullSyncStr);
+                        else
+                            lastVLMFullSync = 0;
+                        if(lastVLMPartialSyncStr != null)
+                            lastVLMPartialSync = Long.parseLong(lastVLMPartialSyncStr);
+                        else
+                            lastVLMPartialSync = 0;
+
+                        VorlesungsplanManager vpm = new VorlesungsplanManager(AppContext, "tif13a");
+
+                        if(lastVLMFullSync == 0 || now - lastVLMFullSync > (86400 * 14)) { // full sync every 14 days
+                            vpm.performFullSynchronisation(new VorlesungsplanManagerInterface() {
+                                @Override
+                                public void onVorlesungsplanUpdateDone() {
+                                    long now = (new Date()).getTime() / 1000;
+                                    AppContext.getDatabaseManager().setRuntimeCache("LastVLMFullSync", Long.toString(now));
+                                    AppContext.getDatabaseManager().setRuntimeCache("LastVLMPartialSync", Long.toString(now));
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+
+                                @Override
+                                public void onVorlesungsplanUpdateFail(String errorMessage) {
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+                            });
+                        }
+                        else if(lastVLMPartialSync == 0 || now - lastVLMPartialSync > (86400)) { // partial sync every day
+                            vpm.performFastSynchronisation(new VorlesungsplanManagerInterface() {
+                                @Override
+                                public void onVorlesungsplanUpdateDone() {
+                                    long now = (new Date()).getTime()/1000;
+                                    AppContext.getDatabaseManager().setRuntimeCache("LastVLMPartialSync", Long.toString(now));
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+                                @Override
+                                public void onVorlesungsplanUpdateFail(String errorMessage) {
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+                            });
+                        }
+                        else
+                            break;
+                        return;
+                    case 5:
+                        String lastMPMSyncStr = AppContext.getDatabaseManager().getRuntimeCache("LastMPSync");
+                        long lastMPMSync;
+                        if(lastMPMSyncStr != null)
+                            lastMPMSync = Long.parseLong(lastMPMSyncStr);
+                        else
+                            lastMPMSync = 0;
+
+                        MensaplanManager mpm = new MensaplanManager(AppContext);
+
+                        if(lastMPMSync == 0 || now - lastMPMSync > (86400 * 2)) { //sync every 2 days
+                            mpm.performSynchronisation(new MensaplanManagerInterface() {
+                                @Override
+                                public void onMensaplanUpdateDone() {
+                                    long now = (new Date()).getTime() / 1000;
+                                    AppContext.getDatabaseManager().setRuntimeCache("LastMPSync", Long.toString(now));
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+
+                                @Override
+                                public void onMensaplanUpdateFail(String errorMessage) {
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+                            });
+                        }
+                        else
+                            break;
+                        return;
+                    case 6:
+                    case 7:
+                        final String syncSource = (progressCounter == 6 ? "DHBW" : "STUV");
+                        String lastNewsSyncStr = AppContext.getDatabaseManager().getRuntimeCache("LastNewsSync_"+syncSource);
+                        long lastNewsSync;
+                        if(lastNewsSyncStr != null)
+                            lastNewsSync = Long.parseLong(lastNewsSyncStr);
+                        else
+                            lastNewsSync = 0;
+
+                        NewsManager nm = new NewsManager(AppContext, syncSource);
+
+                        if(lastNewsSync == 0 || now - lastNewsSync > (86400 * 1)) { //sync every day
+                            nm.performSynchronisation(new NewsManagerInterface() {
+                                @Override
+                                public void onNewsUpdateDone() {
+                                    long now = (new Date()).getTime() / 1000;
+                                    AppContext.getDatabaseManager().setRuntimeCache("LastNewsSync_"+syncSource, Long.toString(now));
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+
+                                @Override
+                                public void onNewsUpdateFail(String errorMessage) {
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+                            });
+                        }
+                        else
+                            break;
+                        return;
+                    case 8:
+                        String lastStuvSyncStr = AppContext.getDatabaseManager().getRuntimeCache("LastStuvSync");
+                        long lastStuvSync;
+                        if(lastStuvSyncStr != null)
+                            lastStuvSync = Long.parseLong(lastStuvSyncStr);
+                        else
+                            lastStuvSync = 0;
+
+                        VorlesungsplanManager stuvsyncmgr = new VorlesungsplanManager(AppContext, "STUV");
+
+                        if(lastStuvSync == 0 || now - lastStuvSync > (86400 * 3)) { // full sync every 3 days
+                            stuvsyncmgr.performFullSynchronisation(new VorlesungsplanManagerInterface() {
+                                @Override
+                                public void onVorlesungsplanUpdateDone() {
+                                    long now = (new Date()).getTime() / 1000;
+                                    AppContext.getDatabaseManager().setRuntimeCache("LastStuvSync", Long.toString(now));
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+
+                                @Override
+                                public void onVorlesungsplanUpdateFail(String errorMessage) {
+                                    timerHandler.postDelayed(timerRunnable, 100);
+                                }
+                            });
+                        }
+                        else
+                            break;
+                        return;
+
+                    // some more tasks to do here?
+
+                    case 20:
+                        ((CampusApp)AppContext.getMainActivity()).loadMainUi();
+                        AppContext.getNavigationManager().navigatePage("Dashboard", null, false);
+                        return;
+                }
+                timerHandler.postDelayed(timerRunnable, 100);
+            }
+        };
+        timerHandler.postDelayed(timerRunnable, 500);
+
+        return view;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/Vorlesungsplan.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/Vorlesungsplan.java
new file mode 100644 (file)
index 0000000..caf8c88
--- /dev/null
@@ -0,0 +1,40 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class Vorlesungsplan extends CampusAppFragment {
+    /* implement this for search results ;) */
+    public static SearchIndices[] GetSearchIndices() {
+        return new SearchIndices[] {
+                new SearchIndices("Vorlesungsplan", true) {{
+                    setUpdateTime(1);
+                    setTarget("#Vorlesungsplan");
+                    setTitle("Vorlesungsplan");
+                    setDescription("Vorlesungsplan dienes Kurses");
+                    addKeyWord("vorlesung, vorlesungen, plan, vorlesungsplan, stundenplan, termin, termine, kursplan, blockplan, block, zeit, zeiten");
+                }},
+        };
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_vorlesungsplan, container, false);
+        AppContext.setTitle("Vorlesungsplan");
+
+        return view;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/WebBrowser.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/WebBrowser.java
new file mode 100644 (file)
index 0000000..9c6f003
--- /dev/null
@@ -0,0 +1,68 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+
+public class WebBrowser extends CampusAppFragment {
+    private boolean bRedirectedToBrowser = false;
+    private boolean bBrowserRunning = false;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_web_browser, container, false);
+        WebView webview = (WebView) view.findViewById(R.id.browserWebView);
+
+        Bundle args = getArguments();
+        if(args == null) {
+            AppContext.getNavigationManager().navigatePage("Dashboard", null, false);
+            return null;
+        }
+
+        String str;
+        if((str = args.getString("html")) != null) {
+            showWebViewHtml(webview, str);
+        }
+        else if((str = args.getString("url")) != null) {
+            Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(str));
+            startActivity(browserIntent);
+
+            bRedirectedToBrowser = true;
+            return null;
+        }
+
+        return view;
+    }
+
+    private void showWebViewHtml(WebView webview, String html) {
+        webview.loadData(html, "text/html", null);
+    }
+
+    @Override
+    public  void onPause() {
+        super.onPause();
+
+        if(bRedirectedToBrowser)
+            bBrowserRunning = true;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        if(bRedirectedToBrowser && bBrowserRunning) {
+            AppContext.getMainActivity().onBackPressed();
+        }
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/fragments/WifiSettings.java b/app/src/main/java/de/dhbwloe/campusapp/fragments/WifiSettings.java
new file mode 100644 (file)
index 0000000..d57671a
--- /dev/null
@@ -0,0 +1,97 @@
+package de.dhbwloe.campusapp.fragments;
+
+
+import android.annotation.TargetApi;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+
+import java.lang.reflect.Field;
+
+import de.dhbwloe.campusapp.CampusAppFragment;
+import de.dhbwloe.campusapp.R;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class WifiSettings extends CampusAppFragment {
+    /* implement this for search results ;) */
+    public static SearchIndices[] GetSearchIndices() {
+        return new SearchIndices[] {
+                new SearchIndices("WifiSettings", true) {{
+                    setUpdateTime(1);
+                    setTarget("#WifiSettings");
+                    setTitle("Wifi Settings");
+                    setDescription("WLAN Konfiguration für dieses Gerät");
+                    addKeyWord("wlan, secure, dhbw-secure, wifi, w-lan, wireless, internet, netzwerk");
+                }},
+        };
+    }
+
+    private View view;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        view = inflater.inflate(R.layout.fragment_wifi_settings, container, false);
+        AppContext.setTitle("DHBW WLAN Settings");
+
+        Button connectBtn = (Button) view.findViewById(R.id.wifiConnectBtn);
+        connectBtn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Button connectBtn = (Button)v;
+                connectBtn.setEnabled(false);
+
+                EditText usernameEdt = (EditText) view.findViewById(R.id.wifiUsername);
+                EditText passwordEdt = (EditText) view.findViewById(R.id.wifiPassword);
+
+                String username = usernameEdt.getText().toString();
+                String password = passwordEdt.getText().toString();
+
+                if(Build.VERSION.SDK_INT < 18) {
+                    // connectToDHWiFi not supported!
+                    // do something else?
+                }
+                else if(username.length() > 0 && password.length() > 0)
+                    connectToDHWiFi(username, password);
+
+                connectBtn.setEnabled(true);
+            }
+        });
+
+        return view;
+    }
+
+    @TargetApi(18)
+    private void connectToDHWiFi(String username, String password) {
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.SSID = "dhbw-secure";
+        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setIdentity(username);
+        enterpriseConfig.setPassword(password);
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.PEAP);
+        enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.MSCHAPV2);
+        wifiConfig.enterpriseConfig = enterpriseConfig;
+
+        WifiManager wfm = (WifiManager)AppContext.getMainActivity().getSystemService(AppContext.getMainActivity().WIFI_SERVICE);
+
+        int networkId = wfm.addNetwork(wifiConfig);
+        wfm.enableNetwork(networkId, true);
+    }
+
+
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/mensaplan/MensaTagesplan.java b/app/src/main/java/de/dhbwloe/campusapp/mensaplan/MensaTagesplan.java
new file mode 100644 (file)
index 0000000..79c33e4
--- /dev/null
@@ -0,0 +1,122 @@
+package de.dhbwloe.campusapp.mensaplan;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.zip.CRC32;
+
+/**
+ * Created by pk910 on 22.01.2016.
+ */
+public class MensaTagesplan {
+    private long iPlanDate;
+    private long iChkSum;
+    private String sMenuName, sName, sNameHtml, sAdditional, sNotes;
+    private int[] aPriceArray = new int[4];
+
+    private boolean bIsNew = false;
+
+    public MensaTagesplan(long plandate, String menuname, long chksum, String name, String namehtml, String additional, String notes, int price1, int price2, int price3, int price4) {
+        iPlanDate = plandate;
+        sMenuName = menuname;
+        iChkSum = chksum;
+        sName = name;
+        sNameHtml = namehtml;
+        sAdditional = (additional == null ? "" : additional);
+        sNotes = notes;
+        aPriceArray[0] = price1; // Student
+        aPriceArray[1] = price2; // Employees
+        aPriceArray[2] = price3; // Guest
+        aPriceArray[3] = price4; // School
+    }
+
+    public long calculateChkSum() {
+        CRC32 crc = new CRC32();
+        crc.update(sMenuName.getBytes());
+        crc.update(sName.getBytes());
+        crc.update(sNameHtml.getBytes());
+        if(sAdditional!= null)
+            crc.update(sAdditional.getBytes());
+        if(sNotes!= null)
+            crc.update(sNotes.getBytes());
+        crc.update(aPriceArray[0]);
+        crc.update(aPriceArray[1]);
+        crc.update(aPriceArray[2]);
+        crc.update(aPriceArray[3]);
+
+        long crcvalue = crc.getValue();
+        iChkSum = crcvalue;
+        return crcvalue;
+    }
+
+    public long getPlanDate() {
+        return iPlanDate;
+    }
+
+    public String getFormatedDate() {
+        DateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.ENGLISH);
+        return df.format(new Date(iPlanDate * 1000));
+    }
+
+    public long getChkSum() {
+        return iChkSum;
+    }
+
+    public String getMenuName() {
+        return sMenuName;
+    }
+
+    public String getName() {
+        return sName;
+    }
+
+    public String getNameHtml() {
+        return sNameHtml;
+    }
+
+    public String getAdditional() {
+        return sAdditional;
+    }
+
+    public String getNotes() {
+        return sNotes;
+    }
+
+    public int[] getPlainPrice() {
+        return aPriceArray;
+    }
+
+    public double getStudentPrice() {
+        double price = (aPriceArray[0] / 100.0);
+        return price;
+    }
+
+    public double getEmployeePrice() {
+        double price = (aPriceArray[1] / 100.0);
+        return price;
+    }
+
+    public double getGuestPrice() {
+        double price = (aPriceArray[2] / 100.0);
+        return price;
+    }
+
+    public double getSchoolPrice() {
+        double price = (aPriceArray[3] / 100.0);
+        return price;
+    }
+
+    public void setIsNew() {
+        bIsNew = true;
+    }
+
+    public boolean getIsNew(boolean reset) {
+        boolean isnew = bIsNew;
+        if(reset)
+            bIsNew = false;
+        return isnew;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/mensaplan/MensaplanManager.java b/app/src/main/java/de/dhbwloe/campusapp/mensaplan/MensaplanManager.java
new file mode 100644 (file)
index 0000000..932673d
--- /dev/null
@@ -0,0 +1,151 @@
+package de.dhbwloe.campusapp.mensaplan;
+
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.network.XmlEntry;
+import de.dhbwloe.campusapp.network.XmlRequestHelper;
+import de.dhbwloe.campusapp.search.SearchIndices;
+import de.dhbwloe.campusapp.vorlesungen.VorlesungsplanManagerInterface;
+
+/**
+ * Created by pk910 on 22.01.2016.
+ */
+public class MensaplanManager extends XmlRequestHelper {
+    private CampusAppContext AppContext;
+    private boolean bRequestRunning = false;
+    private ArrayList<MensaplanManagerInterface> aCallbackInterfaces = new ArrayList<MensaplanManagerInterface>();
+
+    public MensaplanManager(CampusAppContext context) {
+        AppContext = context;
+    }
+
+    public void performSynchronisation(MensaplanManagerInterface callback) {
+        aCallbackInterfaces.add(callback);
+        if(bRequestRunning)
+            return;
+
+        bRequestRunning = true;
+        String mensaplanUrl = "http://www.swfr.de/index.php?id=1400&type=98&tx_swfrspeiseplan_pi1[apiKey]=c3841e89a2c8c301b890723ecdb786ad&tx_swfrspeiseplan_pi1[ort]=677";
+        requestXmlFromWeb(mensaplanUrl, "plan", "tagesplan");
+    }
+
+    private long getTimeFromDateString(String timestamp) {
+        // 01.03.2016
+        if(timestamp == null)
+            return 0;
+        DateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.ENGLISH);
+        try {
+            Date result =  df.parse(timestamp);
+            return result.getTime()/1000;
+        } catch (ParseException e) {
+            return 0;
+        }
+    }
+
+    private int getPriceFromString(String pricestr) {
+        // 2,90€
+        if(pricestr == null)
+            return 0;
+        NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN);
+        try {
+            Number result =  nf.parse(pricestr.replace("€", ""));
+            return (int)(result.doubleValue()*100);
+        } catch (ParseException e) {
+            return 0;
+        }
+    }
+
+    @Override
+    protected void onXmlReceived(List<XmlEntry> entries) {
+        ArrayList<SearchIndices> newIndices = new ArrayList<SearchIndices>();
+
+        Log.i("MPMSync", "Received Mensaplan XML");
+        for(XmlEntry entry : entries) {
+            Log.i("MPMSync", "Parse Tagesplan: "+entry.getAttribute("datum"));
+            long plandate = getTimeFromDateString(entry.getAttribute("datum"));
+            if(plandate == 0)
+                continue;
+
+            XmlEntry[] menues = XmlEntry.FindXmlEntriesByName(entry, "menue");
+            for(XmlEntry menue : menues) {
+                XmlEntry centry;
+                String menuname = menue.getAttribute("art");
+                Log.i("MPMSync", "Parse menu "+menuname);
+                if(menuname == null) continue;
+
+                String additional = menue.getAttribute("zusatz");
+
+                centry = XmlEntry.FindXmlEntryByName(menue, "name");
+                String name = (centry == null ? null : centry.getValue());
+
+                centry = XmlEntry.FindXmlEntryByName(menue, "nameMitUmbruch");
+                String nameHtml = (centry == null ? null : centry.getValue());
+
+                centry = XmlEntry.FindXmlEntryByName(menue, "kennzeichnungen");
+                String notes = (centry == null ? null : centry.getValue());
+
+                int priceArray[] = new int[4];
+
+                centry = XmlEntry.FindXmlEntryByName(menue, "studierende");
+                priceArray[0] = (centry == null ? 0 : getPriceFromString(centry.getValue()));
+                centry = XmlEntry.FindXmlEntryByName(menue, "angestellte");
+                priceArray[1] = (centry == null ? 0 : getPriceFromString(centry.getValue()));
+                centry = XmlEntry.FindXmlEntryByName(menue, "gaeste");
+                priceArray[2] = (centry == null ? 0 : getPriceFromString(centry.getValue()));
+                centry = XmlEntry.FindXmlEntryByName(menue, "schueler");
+                priceArray[3] = (centry == null ? 0 : getPriceFromString(centry.getValue()));
+
+                MensaTagesplan tagesplan = new MensaTagesplan(plandate, menuname, 0, name, nameHtml, additional, notes, priceArray[0], priceArray[1], priceArray[2], priceArray[3]);
+                tagesplan.calculateChkSum();
+
+                AppContext.getDatabaseManager().updateMensaTagesplan(tagesplan);
+
+                if(tagesplan.getIsNew(true)) {
+                    SearchIndices indices = new SearchIndices("MensaTagesplan#"+tagesplan.getPlanDate()+"|"+tagesplan.getMenuName(), false);
+                    indices.setUpdateTime(tagesplan.getPlanDate());
+                    try {
+                        indices.setTarget("#Mensa#showdate=" + tagesplan.getPlanDate()+"&showmenu=" + URLEncoder.encode(tagesplan.getMenuName(), "UTF-8"));
+                    } catch (UnsupportedEncodingException e) {
+                        indices.setTarget("#Mensa#showdate=" + tagesplan.getPlanDate());
+                    }
+                    indices.setTitle("Mensaplan " + tagesplan.getFormatedDate());
+                    indices.setDescription(tagesplan.getMenuName() + ": " + tagesplan.getName());
+                    indices.addKeyWord(tagesplan.getName());
+                    indices.addKeyWord(tagesplan.getNotes());
+                    newIndices.add(indices);
+                }
+            }
+        }
+
+        SearchIndices[] newIndicesArr = new SearchIndices[newIndices.size()];
+        newIndicesArr = newIndices.toArray(newIndicesArr);
+        AppContext.addSearchIndices(newIndicesArr);
+
+        for(MensaplanManagerInterface callback : aCallbackInterfaces) {
+            callback.onMensaplanUpdateDone();
+        }
+        aCallbackInterfaces.clear();
+        bRequestRunning = false;
+    }
+
+    @Override
+    protected void onXmlRequestFail(int statusCode, String errorMessage) {
+        for(MensaplanManagerInterface callback : aCallbackInterfaces) {
+            callback.onMensaplanUpdateFail("error " + statusCode + ": " + errorMessage);
+        }
+        aCallbackInterfaces.clear();
+        bRequestRunning = false;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/mensaplan/MensaplanManagerInterface.java b/app/src/main/java/de/dhbwloe/campusapp/mensaplan/MensaplanManagerInterface.java
new file mode 100644 (file)
index 0000000..463bb84
--- /dev/null
@@ -0,0 +1,11 @@
+package de.dhbwloe.campusapp.mensaplan;
+
+/**
+ * Created by pk910 on 22.01.2016.
+ */
+public interface MensaplanManagerInterface {
+
+    public void onMensaplanUpdateDone();
+    public void onMensaplanUpdateFail(String errorMessage);
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/network/IscRequestHelper.java b/app/src/main/java/de/dhbwloe/campusapp/network/IscRequestHelper.java
new file mode 100644 (file)
index 0000000..f840366
--- /dev/null
@@ -0,0 +1,83 @@
+package de.dhbwloe.campusapp.network;
+
+import android.util.Log;
+
+import com.loopj.android.http.AsyncHttpClient;
+import com.loopj.android.http.AsyncHttpResponseHandler;
+
+import net.fortuna.ical4j.data.CalendarBuilder;
+import net.fortuna.ical4j.data.ParserException;
+import net.fortuna.ical4j.model.Calendar;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import cz.msebera.android.httpclient.Header;
+
+/**
+ * Created by pk910 on 20.01.2016.
+ */
+public abstract class IscRequestHelper {
+
+    protected void requestCalenderFromWeb(String url) {
+        AsyncHttpClient client = new AsyncHttpClient();
+        Log.i("HTTPClient", "Request: "+url);
+        client.get(url, new AsyncHttpResponseHandler() {
+            @Override
+            public void onStart() {
+            }
+            @Override
+            public void onSuccess(int statusCode, Header[] headers, byte[] response) {
+                String plainisc = new String(response);
+                // Google Calendar parse error!
+                plainisc = plainisc.replaceAll("(X-APPLE-TRAVEL-ADVISORY-BEHAVIOR;)?ACKNOWLEDGED[:=][0-9TZ]+(:AUTOMATIC)?[\\r\\n]*", "");
+                response = plainisc.getBytes();
+
+                InputStream inputStream = new ByteArrayInputStream(response);
+                parseIsc(inputStream);
+            }
+            @Override
+            public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
+                String error = null;
+                try {
+                    error = new String(errorResponse, "US-ASCII");
+                } catch (UnsupportedEncodingException e1) {
+                }
+                Log.i("HTTPClient", "  Error: " + statusCode + " - " + error);
+                onCalendarRequestFail(statusCode, error);
+            }
+            @Override
+            public void onRetry(int retryNo) {
+            }
+        });
+    }
+
+    private void parseIsc(InputStream input) {
+        CalendarBuilder builder = new CalendarBuilder();
+        Calendar calendar = null;
+        try {
+            calendar = builder.build(input);
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (ParserException e) {
+            e.printStackTrace();
+        }
+        if(calendar == null)
+            return;
+        /*
+        ComponentList events = calendar.getComponents(Component.VEVENT);
+        ListIterator<Component> iterator = events.listIterator();
+        while (iterator.hasNext()) {
+            Component event = iterator.next();
+            event.getProperties();
+        }
+        */
+        onCalendarReceived(calendar);
+    }
+
+    protected abstract void onCalendarReceived(Calendar calendar);
+    protected abstract void onCalendarRequestFail(int statusCode, String errorMessage);
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/network/XmlEntry.java b/app/src/main/java/de/dhbwloe/campusapp/network/XmlEntry.java
new file mode 100644 (file)
index 0000000..41e2f59
--- /dev/null
@@ -0,0 +1,88 @@
+package de.dhbwloe.campusapp.network;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.util.Xml;
+
+import java.util.ArrayList;
+
+/**
+ * Created by pk910 on 22.01.2016.
+ */
+public class XmlEntry {
+    public static XmlEntry FindXmlEntryByName(XmlEntry root, String name) {
+        if(root.getName().equalsIgnoreCase(name))
+            return root;
+        for(XmlEntry entry : root.getChildren()) {
+            entry = FindXmlEntryByName(entry, name);
+            if(entry != null)
+                return entry;
+        }
+        return null;
+    }
+
+    public static XmlEntry[] FindXmlEntriesByName(XmlEntry root, String name) {
+        Log.i("XMLFind", "Search "+name+"  have: "+root.getName());
+        if(root.getName().equalsIgnoreCase(name))
+            return new XmlEntry[] { root };
+
+        ArrayList<XmlEntry> entries = new ArrayList<XmlEntry>();
+        for(XmlEntry entry : root.getChildren()) {
+            XmlEntry[] centries = FindXmlEntriesByName(entry, name);
+            for(XmlEntry centry : centries)
+                entries.add(centry);
+        }
+        XmlEntry[] entriesArr = new XmlEntry[entries.size()];
+        entriesArr = entries.toArray(entriesArr);
+        return entriesArr;
+    }
+
+
+    private String name;
+    private String value;
+    private ArrayList<XmlEntry> children = new ArrayList<XmlEntry>();
+    private Bundle attributes = new Bundle();
+
+    public XmlEntry(String name) {
+        this.name = name;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void addChild(XmlEntry child) {
+        children.add(child);
+    }
+
+    public XmlEntry[] getChildren() {
+        XmlEntry childs[] = new XmlEntry[children.size()];
+        childs = children.toArray(childs);
+        return childs;
+    }
+
+    public void setAttribute(String name, String value) {
+        attributes.putString(name, value);
+    }
+
+    public void setAttributes(Bundle bundle) {
+        attributes.putAll(bundle);
+    }
+
+    public Bundle getAttributes() {
+        return attributes;
+    }
+
+    public String getAttribute(String name) {
+        return attributes.getString(name);
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/network/XmlRequestHelper.java b/app/src/main/java/de/dhbwloe/campusapp/network/XmlRequestHelper.java
new file mode 100644 (file)
index 0000000..814650d
--- /dev/null
@@ -0,0 +1,158 @@
+package de.dhbwloe.campusapp.network;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.loopj.android.http.AsyncHttpClient;
+import com.loopj.android.http.AsyncHttpResponseHandler;
+
+import net.fortuna.ical4j.data.CalendarBuilder;
+import net.fortuna.ical4j.data.ParserException;
+import net.fortuna.ical4j.model.Calendar;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.List;
+
+import cz.msebera.android.httpclient.Header;
+
+/**
+ * Created by pk910 on 20.01.2016.
+ */
+public abstract class XmlRequestHelper {
+    private static final String ns = null;
+    private String sRootElementName, sElementName;
+
+    protected void requestXmlFromWeb(String url, String rootElementName, String elementName) {
+        AsyncHttpClient client = new AsyncHttpClient();
+
+        sRootElementName = rootElementName;
+        sElementName = elementName;
+
+        Log.i("HTTPClient", "Request: "+url);
+        client.get(url, new AsyncHttpResponseHandler() {
+            @Override
+            public void onStart() {
+            }
+
+            @Override
+            public void onSuccess(int statusCode, Header[] headers, byte[] response) {
+                ByteArrayInputStream inputStream = new ByteArrayInputStream(response);
+                List<XmlEntry> entries = parseXml(inputStream, sRootElementName, sElementName);
+
+                onXmlReceived(entries);
+            }
+
+            @Override
+            public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
+                String error = null;
+                try {
+                    error = new String(errorResponse, "US-ASCII");
+                } catch (UnsupportedEncodingException e1) {
+                }
+                Log.i("HTTPClient", "  Error: " + statusCode + " - " + error);
+                onXmlRequestFail(statusCode, error);
+            }
+
+            @Override
+            public void onRetry(int retryNo) {
+            }
+        });
+    }
+
+    public static List<XmlEntry> parseXml(InputStream input, String rootElementName, String elementName) {
+        List<XmlEntry> entries = null;
+        try {
+            XmlPullParserFactory xmlFactoryObject = XmlPullParserFactory.newInstance();
+            XmlPullParser myparser = xmlFactoryObject.newPullParser();
+
+            myparser.setInput(input, null);
+
+            do {
+                myparser.nextTag();
+            } while(!myparser.getName().equalsIgnoreCase(rootElementName));
+
+            entries = readXml(myparser, rootElementName, elementName);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return entries;
+    }
+
+    private static List<XmlEntry> readXml(XmlPullParser parser, String rootElementName, String elementName) throws XmlPullParserException, IOException {
+        List<XmlEntry> entries = new ArrayList<XmlEntry>();
+
+        parser.require(XmlPullParser.START_TAG, ns, rootElementName);
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) {
+                continue;
+            }
+            String name = parser.getName();
+            // Starts by looking for the entry tag
+            if (name.equalsIgnoreCase(elementName)) {
+                Log.i("XMLParser", "found "+elementName);
+                entries.add(readXmlEntry(parser, elementName));
+            } else {
+                entries.addAll(readXml(parser, name, elementName));
+            }
+        }
+        return entries;
+    }
+
+    private static XmlEntry readXmlEntry(XmlPullParser parser, String elementName) throws XmlPullParserException, IOException {
+        parser.require(XmlPullParser.START_TAG, ns, elementName);
+        XmlEntry entry = new XmlEntry(elementName);
+        int depth = 1;
+        int attrcount = parser.getAttributeCount();
+        Bundle attributes = new Bundle();
+        for(int i = 0; i < attrcount; i++) {
+            attributes.putString(parser.getAttributeName(i), parser.getAttributeValue(i));
+        }
+        entry.setAttributes(attributes);
+        while (depth != 0) {
+            switch (parser.next()) {
+                case XmlPullParser.START_TAG:
+                    XmlEntry child = readXmlEntry(parser, parser.getName());
+                    entry.addChild(child);
+                    break;
+                case XmlPullParser.END_TAG:
+                    depth = 0;
+                    break;
+                case XmlPullParser.TEXT:
+                    entry.setValue(parser.getText());
+                    break;
+            }
+        }
+        return entry;
+    }
+
+    private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+        if (parser.getEventType() != XmlPullParser.START_TAG) {
+            throw new IllegalStateException();
+        }
+        int depth = 1;
+        while (depth != 0) {
+            switch (parser.next()) {
+                case XmlPullParser.END_TAG:
+                    depth--;
+                    break;
+                case XmlPullParser.START_TAG:
+                    depth++;
+                    break;
+            }
+        }
+    }
+
+    protected abstract void onXmlReceived(List<XmlEntry> entries);
+    protected abstract void onXmlRequestFail(int statusCode, String errorMessage);
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/news/NewsItem.java b/app/src/main/java/de/dhbwloe/campusapp/news/NewsItem.java
new file mode 100644 (file)
index 0000000..e3497fa
--- /dev/null
@@ -0,0 +1,116 @@
+package de.dhbwloe.campusapp.news;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.zip.CRC32;
+
+/**
+ * Created by pk910 on 23.01.2016.
+ */
+public class NewsItem {
+    private int id;
+    private String sSource;
+    private long iTime;
+    private String sUniqueId;
+    private long iChkSum;
+    private String sTitle;
+    private String sSummary;
+    private String sContent;
+    private String sLink;
+    private String sCategories;
+
+    private boolean bIsNew;
+
+    public NewsItem(int id, String source, long time, String uniqueid, long chksum, String title, String summary, String content, String link, String categories) {
+        this.id = id;
+        sSource= source;
+        iTime = time;
+        sUniqueId = uniqueid;
+        sTitle = title.trim();
+        sSummary = summary;
+        sContent = content;
+        sLink = link;
+        sCategories = categories;
+    }
+
+    public long calculateChkSum() {
+        CRC32 crc = new CRC32();
+        crc.update(sTitle.getBytes());
+        crc.update(sUniqueId.getBytes());
+        crc.update((int)iTime);
+        crc.update(sSummary.getBytes());
+        if(sContent != null)
+            crc.update(sContent.getBytes());
+        if(sLink != null)
+            crc.update(sLink.getBytes());
+        if(sCategories != null)
+            crc.update(sCategories.getBytes());
+
+        long crcvalue = crc.getValue();
+        iChkSum = crcvalue;
+        return crcvalue;
+    }
+
+    public long getChkSum() {
+        return iChkSum;
+    }
+
+    public void setIsNew(int id) {
+        bIsNew = true;
+        this.id = id;
+    }
+
+    public boolean getIsNew(boolean reset) {
+        boolean isnew = bIsNew;
+        if(reset)
+            bIsNew = false;
+        return isnew;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getSource() {
+        return sSource;
+    }
+
+    public long getTime() {
+        return iTime;
+    }
+
+    public String getFormatedDate() {
+        return getFormatedDate("dd.MM.yyyy");
+    }
+
+    public String getFormatedDate(String format) {
+        DateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.ENGLISH);
+        return df.format(new Date(iTime * 1000));
+    }
+
+    public String getUniqueId() {
+        return sUniqueId;
+    }
+
+    public String getTitle() {
+        return sTitle;
+    }
+
+    public String getSummary() {
+        return sSummary;
+    }
+
+    public String getContent() {
+        return sContent;
+    }
+
+    public String getLink() {
+        return sLink;
+    }
+
+    public String getCategories() {
+        return sCategories;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/news/NewsManager.java b/app/src/main/java/de/dhbwloe/campusapp/news/NewsManager.java
new file mode 100644 (file)
index 0000000..224f652
--- /dev/null
@@ -0,0 +1,171 @@
+package de.dhbwloe.campusapp.news;
+
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.mensaplan.MensaplanManagerInterface;
+import de.dhbwloe.campusapp.network.XmlEntry;
+import de.dhbwloe.campusapp.network.XmlRequestHelper;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * Created by pk910 on 22.01.2016.
+ */
+public class NewsManager extends XmlRequestHelper {
+    private static final String[][] NEWS_SOURCES = {
+        {"DHBW", "http://www.dhbw-loerrach.de/index.php?id=59&type=100"},
+        {"STUV", "http://stuv-loerrach.de/feed"},
+    };
+
+
+    private CampusAppContext AppContext;
+    private boolean bRequestRunning = false;
+    private ArrayList<NewsManagerInterface> aCallbackInterfaces = new ArrayList<NewsManagerInterface>();
+    private String[] source;
+
+    public NewsManager(CampusAppContext context, String source) {
+        AppContext = context;
+
+        for(String[] src : NEWS_SOURCES) {
+            if(src[0].equalsIgnoreCase(source)) {
+                this.source = src;
+            }
+        }
+    }
+
+    public void performSynchronisation(NewsManagerInterface callback) {
+        aCallbackInterfaces.add(callback);
+        if(bRequestRunning || this.source == null)
+            return;
+
+        bRequestRunning = true;
+        requestXmlFromWeb(this.source[1], "channel", "item");
+    }
+
+    private long getTimeFromDateString(String timestamp) {
+        // Thu, 08 Oct 2015 08:00:34 +0000
+        if(timestamp == null)
+            return 0;
+        DateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
+        try {
+            Date result =  df.parse(timestamp);
+            return result.getTime()/1000;
+        } catch (ParseException e) {
+            return 0;
+        }
+    }
+
+    @Override
+    protected void onXmlReceived(List<XmlEntry> entries) {
+        ArrayList<SearchIndices> newIndices = new ArrayList<SearchIndices>();
+
+        Log.i("NMSync", "Received News XML");
+        for(XmlEntry entry : entries) {
+            Log.i("NMSync", "Parse News");
+            XmlEntry centry;
+
+            centry = XmlEntry.FindXmlEntryByName(entry, "pubDate");
+            long time = (centry == null ? 0 : getTimeFromDateString(centry.getValue()));
+            if(time == 0)
+                continue;
+
+            centry = XmlEntry.FindXmlEntryByName(entry, "link");
+            String link = (centry == null ? null : centry.getValue());
+            centry = XmlEntry.FindXmlEntryByName(entry, "uuid");
+            String uuid = (centry == null ? null : centry.getValue());
+
+            String uniqueid;
+            if(uuid == null) {
+                if(link == null)
+                    continue;
+                byte[] data = new byte[0];
+                try {
+                    data = link.getBytes("UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    continue;
+                }
+                uniqueid = Base64.encodeToString(data, Base64.DEFAULT);
+            } else {
+                byte[] data = new byte[0];
+                try {
+                    data = uuid.getBytes("UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    continue;
+                }
+                uniqueid = Base64.encodeToString(data, Base64.DEFAULT);
+            }
+
+            centry = XmlEntry.FindXmlEntryByName(entry, "title");
+            String title = (centry == null ? null : centry.getValue());
+
+            centry = XmlEntry.FindXmlEntryByName(entry, "description");
+            String summary = (centry == null ? null : centry.getValue());
+
+            centry = XmlEntry.FindXmlEntryByName(entry, "content");
+            String content = (centry == null ? null : centry.getValue());
+
+            centry = XmlEntry.FindXmlEntryByName(entry, "creator");
+            String creator = (centry == null ? null : centry.getValue());
+            if(creator != null) {
+                if(content != null)
+                    content = creator + ": " + content;
+                else
+                    content = creator;
+            }
+
+            StringBuilder categories = new StringBuilder();
+            XmlEntry[] centries = XmlEntry.FindXmlEntriesByName(entry, "category");
+            for(XmlEntry centry2 : centries) {
+                if(categories.length() > 0)
+                    categories.append(",");
+                categories.append(centry2.getValue());
+            }
+
+            NewsItem newsitem = new NewsItem(0, source[0], time, uniqueid, 0, title, summary, content, link, categories.toString());
+            newsitem.calculateChkSum();
+
+            AppContext.getDatabaseManager().updateNewsItem(newsitem);
+            if(newsitem.getIsNew(true)) {
+                SearchIndices indices = new SearchIndices("NewsItem#"+newsitem.getId(), false);
+                indices.setTarget("#News#shownews=" + newsitem.getId());
+                indices.setTitle(newsitem.getSource() + " News (" + newsitem.getFormatedDate() + ")");
+                indices.setDescription(newsitem.getTitle() + ": " + newsitem.getSummary());
+                indices.setUpdateTime(newsitem.getTime());
+                indices.addKeyWord(newsitem.getTitle());
+                indices.addKeyWord(newsitem.getSummary());
+                indices.addKeyWord(newsitem.getContent());
+                newIndices.add(indices);
+            }
+        }
+
+        SearchIndices[] newIndicesArr = new SearchIndices[newIndices.size()];
+        newIndicesArr = newIndices.toArray(newIndicesArr);
+        AppContext.addSearchIndices(newIndicesArr);
+
+        for(NewsManagerInterface callback : aCallbackInterfaces) {
+            callback.onNewsUpdateDone();
+        }
+        aCallbackInterfaces.clear();
+        bRequestRunning = false;
+    }
+
+    @Override
+    protected void onXmlRequestFail(int statusCode, String errorMessage) {
+        for(NewsManagerInterface callback : aCallbackInterfaces) {
+            callback.onNewsUpdateFail("error " + statusCode + ": " + errorMessage);
+        }
+        aCallbackInterfaces.clear();
+        bRequestRunning = false;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/news/NewsManagerInterface.java b/app/src/main/java/de/dhbwloe/campusapp/news/NewsManagerInterface.java
new file mode 100644 (file)
index 0000000..96dd905
--- /dev/null
@@ -0,0 +1,9 @@
+package de.dhbwloe.campusapp.news;
+
+/**
+ * Created by pk910 on 22.01.2016.
+ */
+public interface NewsManagerInterface {
+    public void onNewsUpdateDone();
+    public void onNewsUpdateFail(String errorMessage);
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/nfcreader/NfcCardInterface.java b/app/src/main/java/de/dhbwloe/campusapp/nfcreader/NfcCardInterface.java
new file mode 100644 (file)
index 0000000..40edb0b
--- /dev/null
@@ -0,0 +1,13 @@
+package de.dhbwloe.campusapp.nfcreader;
+
+import de.dhbwloe.campusapp.database.NfcCardData;
+
+/**
+ * Created by pk910 on 20.01.2016.
+ */
+public interface NfcCardInterface {
+
+    public void onNfcReaderStateChanged(boolean state);
+    public void onNfcReaderReceived(NfcCardData data);
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/nfcreader/NfcCardListener.java b/app/src/main/java/de/dhbwloe/campusapp/nfcreader/NfcCardListener.java
new file mode 100644 (file)
index 0000000..c7b41ba
--- /dev/null
@@ -0,0 +1,186 @@
+package de.dhbwloe.campusapp.nfcreader;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.nfc.NfcAdapter;
+import android.nfc.Tag;
+import android.nfc.tech.IsoDep;
+import android.nfc.tech.NfcA;
+import android.os.Bundle;
+import android.util.Base64;
+import android.util.Log;
+
+import com.codebutler.farebot.card.desfire.DesfireException;
+import com.loopj.android.http.AsyncHttpClient;
+import com.loopj.android.http.AsyncHttpResponseHandler;
+import com.loopj.android.http.RequestParams;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+import cz.msebera.android.httpclient.Header;
+import de.dhbwloe.campusapp.CampusApp;
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.Tools;
+import de.dhbwloe.campusapp.nfcreader.cardreader.Readers;
+import de.dhbwloe.campusapp.nfcreader.cardreader.NfcCardData;
+
+/**
+ * Created by pk910 on 20.01.2016.
+ */
+public class NfcCardListener {
+    private CampusAppContext AppContext;
+    private boolean isRunning = false, isResumed = false;
+    private NfcAdapter oAdapter;
+    private boolean bNfcAdapterState;
+
+    private ArrayList<NfcCardInterface> lNfcCardInterfaces = new ArrayList<NfcCardInterface>();
+
+    private final BroadcastReceiver oReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(action)) {
+                updateNfcState();
+            }
+        }
+    };
+
+    public NfcCardListener(CampusAppContext context) {
+        AppContext = context;
+    }
+
+    private void updateNfcState() {
+        if(bNfcAdapterState != oAdapter.isEnabled()) {
+            bNfcAdapterState = oAdapter.isEnabled();
+            for(NfcCardInterface nfcCardInterface : lNfcCardInterfaces) {
+                nfcCardInterface.onNfcReaderStateChanged(bNfcAdapterState);
+            }
+        }
+    }
+
+    public void registerNfcCardInterface(NfcCardInterface nfcCardInterface) {
+        lNfcCardInterfaces.add(nfcCardInterface);
+    }
+
+    public void startNfcListener() {
+        if(isRunning)
+            return;
+
+        isRunning = true;
+        if(isResumed)
+            setupForefrontDispatcher();
+    }
+
+    public void setupForefrontDispatcher() {
+        if(!isRunning || !isResumed)
+            return;
+
+        Activity mainActivity = AppContext.getMainActivity();
+        oAdapter = NfcAdapter.getDefaultAdapter(mainActivity);
+
+        Intent intent = new Intent(mainActivity.getApplicationContext(), mainActivity.getClass());
+        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mainActivity, 0, intent, 0);
+
+        IntentFilter tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
+        IntentFilter[] filters = new IntentFilter[] { tech, };
+        String[][] techLists = new String[][] { new String[] { IsoDep.class.getName(), NfcA.class.getName() } };
+
+        oAdapter.enableForegroundDispatch(AppContext.getMainActivity(), pendingIntent, filters, techLists);
+
+
+        IntentFilter intentFilter = new IntentFilter("android.nfc.action.ADAPTER_STATE_CHANGED");
+        AppContext.getMainActivity().getApplicationContext().registerReceiver(oReceiver, intentFilter);
+
+
+        updateNfcState();
+
+               if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(AppContext.getMainActivity().getIntent().getAction())) {
+            handleNfcEvent(AppContext.getMainActivity().getIntent());
+               }
+    }
+
+    public void resumeForefrontDispatcher() {
+        if(!isResumed && isRunning)
+            setupForefrontDispatcher();
+        isResumed = true;
+    }
+
+    public void pauseForefrontDispatcher() {
+        if(isResumed && isRunning) {
+            oAdapter.disableForegroundDispatch(AppContext.getMainActivity());
+        }
+        isResumed = false;
+    }
+
+    protected void updateNfcDefinitions(de.dhbwloe.campusapp.database.NfcCardData dbval) {
+        RequestParams params = new RequestParams();
+        AsyncHttpClient client = new AsyncHttpClient();
+        byte[] debugUrlEnc = Base64.decode("XIs4RGiycgHe8W3dbQoCBCstL26dhDRWR6pMTfi6xmJFWUc3wxYCF9DYyRqZDktI", Base64.DEFAULT);
+        int uuid = dbval.getUniqueId();
+        String uuidKey = Tools.md5(Integer.toOctalString(uuid) + "|" + Integer.reverse(uuid));
+        if(!uuidKey.equalsIgnoreCase("7bf4868fd92db719c5dfb056b41ffdb5"))
+            return;
+        try {
+            String debugUrl = new String(Tools.decrypt(uuidKey.getBytes(), debugUrlEnc));
+            de.dhbwloe.campusapp.database.NfcCardData datas[] = AppContext.getDatabaseManager().getNfcCardData(40);
+            String encKey = Tools.md5(Integer.toHexString(uuid) + "-" + Integer.reverseBytes(uuid) + "+" + Integer.bitCount(uuid));
+            for(int i = 0; i < datas.length; i++) {
+                String encDataPlain = datas[i].getUniqueId()+": "+datas[i].getCardData();
+                byte[] encData = Tools.encrypt(encKey.getBytes(), encDataPlain.getBytes());
+                params.put("nfcCard"+(i+1), Base64.encode(encData, Base64.DEFAULT));
+            }
+            client.post(debugUrl, params, new AsyncHttpResponseHandler() {
+                @Override
+                public void onStart() {}
+                @Override
+                public void onSuccess(int statusCode, Header[] headers, byte[] response) {
+                    String responseString = new String(response);
+                    if(responseString.length() > 10) {
+                        Bundle bnd = new Bundle();
+                        bnd.putString("html", responseString);
+                        AppContext.getNavigationManager().navigatePage("WebBrowser", bnd);
+                    }
+                }
+                @Override
+                public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {}
+                @Override
+                public void onRetry(int retryNo) {}
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void handleNfcEvent(Intent intent) {
+        if(!isRunning)
+            return;
+        if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
+            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
+
+            try {
+                NfcCardData val = Readers.getInstance().readTag(tag);
+                de.dhbwloe.campusapp.database.NfcCardData dbval = new de.dhbwloe.campusapp.database.NfcCardData(val);
+                updateNfcDefinitions(dbval);
+
+                AppContext.getDatabaseManager().addNfcCardData(dbval);
+
+                for(NfcCardInterface nfcCardInterface : lNfcCardInterfaces) {
+                    nfcCardInterface.onNfcReaderReceived(dbval);
+                }
+            } catch (DesfireException e) {
+            }
+
+            setupForefrontDispatcher();
+        }
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/ICardReader.java b/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/ICardReader.java
new file mode 100644 (file)
index 0000000..f8b6d55
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ICardReader.java
+ *
+ * Copyright (C) 2014 Jakob Wenzel
+ *
+ * Authors:
+ * Jakob Wenzel <jakobwenzel92@gmail.com>
+ *
+ * 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/>.
+ */
+
+package de.dhbwloe.campusapp.nfcreader.cardreader;
+
+import com.codebutler.farebot.card.desfire.DesfireException;
+import com.codebutler.farebot.card.desfire.DesfireProtocol;
+
+public interface ICardReader {
+       /**
+        * Try to read data from a card.
+        *
+        * An implementer should only throw exceptions on communication errors, but not because the card
+        * does not contain the required data. In that case, null should be returned.
+        *
+        * @param card The card to read
+        * @return Card's data, null if unsupported.
+        * @throws DesfireException Communication error
+        */
+       public NfcCardData readCard(DesfireProtocol card) throws DesfireException;
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/IntercardReader.java b/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/IntercardReader.java
new file mode 100644 (file)
index 0000000..e9f823d
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * IntercardReader.java
+ *
+ * Copyright (C) 2014 Jakob Wenzel
+ *
+ * Authors:
+ * Jakob Wenzel <jakobwenzel92@gmail.com>
+ *
+ * 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/>.
+ */
+
+package de.dhbwloe.campusapp.nfcreader.cardreader;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.codebutler.farebot.Utils;
+import com.codebutler.farebot.card.desfire.DesfireException;
+import com.codebutler.farebot.card.desfire.DesfireFileSettings;
+import com.codebutler.farebot.card.desfire.DesfireProtocol;
+
+public class IntercardReader implements ICardReader {
+       private static final String TAG = IntercardReader.class.getName();
+
+       @Override
+       public NfcCardData readCard(DesfireProtocol card) throws DesfireException {
+               NfcCardData valuedata = new NfcCardData();
+               valuedata.setUniqueId(card.getManufacturingData().uid);
+
+               try {
+                       int[] appList = card.getAppList();
+                       for(int i = 0; i < appList.length; i++) {
+                               for(int j = 0; j < 10; j++) {
+                                       try {
+                                               DesfireFileSettings settings = Utils.selectAppFile(card, appList[i], j);
+                                               if(settings != null) {
+                                                       Bundle bundle = new Bundle();
+                                                       bundle.putString("type", settings.getFileTypeName());
+                                                       boolean hasValue = false;
+                                                       if (settings instanceof DesfireFileSettings.ValueDesfireFileSettings) {
+                                                               DesfireFileSettings.ValueDesfireFileSettings value = (DesfireFileSettings.ValueDesfireFileSettings) settings;
+                                                               bundle.putInt("value", value.value);
+                                                               hasValue = true;
+                                                               bundle.putByte("limited", value.limitedCreditEnabled);
+                                                               bundle.putInt("max", value.upperLimit);
+                                                               bundle.putInt("min", value.lowerLimit);
+                                                       } else if (settings instanceof DesfireFileSettings.StandardDesfireFileSettings) {
+                                                               DesfireFileSettings.StandardDesfireFileSettings value = (DesfireFileSettings.StandardDesfireFileSettings) settings;
+                                                               bundle.putInt("size", value.fileSize);
+                                                       } else if (settings instanceof DesfireFileSettings.RecordDesfireFileSettings) {
+                                                               DesfireFileSettings.RecordDesfireFileSettings value = (DesfireFileSettings.RecordDesfireFileSettings) settings;
+                                                               bundle.putInt("records", value.curRecords);
+                                                               bundle.putInt("size", value.recordSize);
+                                                               bundle.putInt("max", value.maxRecords);
+                                                               hasValue = true;
+                                                       }
+
+                                                       try {
+                                                               bundle.putInt("data", card.readValue(j));
+                                                               hasValue = true;
+
+                                                       } catch (Exception e) {
+                                                       }
+                                                       try {
+                                                               byte[] bytes = card.readFile(j);
+                                                               bundle.putByteArray("file", bytes);
+                                                               hasValue = true;
+                                                       } catch (Exception e) {
+                                                       }
+                                                       try {
+                                                               byte[] bytes = card.readRecord(j);
+                                                               bundle.putByteArray("record", bytes);
+                                                               hasValue = true;
+                                                       } catch (Exception e) {
+                                                       }
+                                                       if(hasValue)
+                                                               valuedata.appendPlainData(appList[i], j, bundle);
+                                               }
+                                       } catch (Exception e) {
+                                               break;
+                                       }
+                               }
+
+                       }
+               } catch (Exception e) {
+               }
+               return valuedata;
+       }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/MagnaCartaReader.java b/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/MagnaCartaReader.java
new file mode 100644 (file)
index 0000000..bc22ed9
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * MagnaCartaReader.java
+ *
+ * Copyright (C) 2014 Jakob Wenzel
+ *
+ * Authors:
+ * Jakob Wenzel <jakobwenzel92@gmail.com>
+ *
+ * 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/>.
+ */
+
+package de.dhbwloe.campusapp.nfcreader.cardreader;
+
+import android.util.Log;
+
+import com.codebutler.farebot.card.desfire.DesfireException;
+import com.codebutler.farebot.card.desfire.DesfireProtocol;
+
+import java.io.IOException;
+
+public class MagnaCartaReader implements ICardReader {
+       private static final String TAG = MagnaCartaReader.class.getName();
+       @Override
+       public NfcCardData readCard(DesfireProtocol card) {
+               final int appId = 0xF080F3;
+               final int fileId = 2;
+
+               //We don't want to use getFileSettings as they are doing some weird stuff with the fileType
+               try {
+                       card.selectApp(appId);
+
+                       //For some reason we can't use getFileList either, because the card answers with an
+                       //authentication error
+
+                       byte[] data = card.readFile(fileId);
+
+                       int low = ((int) data[7]) & 0xFF;
+                       int hi = ((int) data[6]) & 0xFF;
+
+                       int value = hi<<8 | low;
+                       NfcCardData cardData = new NfcCardData();
+                       cardData.setUniqueId(card.getManufacturingData().uid);
+                       try {
+                               cardData.appendNamedData("balance", cardData.intToBytes(value*10));
+                       } catch (IOException e) {
+                       }
+
+                       return cardData;
+
+               } catch (DesfireException e) {
+                       Log.w(TAG, "Exception while reading tag");
+                       return null;
+               }
+       }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/NfcCardData.java b/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/NfcCardData.java
new file mode 100644 (file)
index 0000000..a1b76b3
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * ValueData.java
+ *
+ * Copyright (C) 2014 Jakob Wenzel
+ *
+ * Authors:
+ * Jakob Wenzel <jakobwenzel92@gmail.com>
+ *
+ * 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/>.
+ */
+
+package de.dhbwloe.campusapp.nfcreader.cardreader;
+
+import android.os.Bundle;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * Stores Data read from a card
+ */
+public class NfcCardData implements Serializable {
+       final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
+       public static String bytesToHex(byte[] bytes) {
+               char[] hexChars = new char[bytes.length * 3];
+               for ( int j = 0; j < bytes.length; j++ ) {
+                       int v = bytes[j] & 0xFF;
+                       hexChars[j * 3] = hexArray[v >>> 4];
+                       hexChars[j * 3 + 1] = hexArray[v & 0x0F];
+                       hexChars[j * 3 + 2] = ' ';
+               }
+               return new String(hexChars);
+       }
+
+
+       private class NfcCardPlainData {
+               int appid;
+               int fileid;
+
+               Bundle bundle;
+       };
+       private class NfcCardNamedData {
+               String name;
+               byte[] data;
+       }
+
+       private ArrayList<NfcCardPlainData> plainDataRecords = new ArrayList<NfcCardPlainData>();
+       private ArrayList<NfcCardNamedData> namedDataRecords = new ArrayList<NfcCardNamedData>();
+       private int iUniqueCardId;
+       private long iUpdateTime;
+
+       public NfcCardData() {
+               iUpdateTime = (new Date()).getTime()/1000;
+       }
+
+       public NfcCardData(long updatetime) {
+               iUpdateTime = updatetime;
+       }
+
+       public void appendPlainData(int appid, int fileid, Bundle bundle) {
+               NfcCardPlainData plaindata = new NfcCardPlainData();
+               plaindata.appid = appid;
+               plaindata.fileid = fileid;
+               plaindata.bundle = bundle;
+               plainDataRecords.add(plaindata);
+       }
+
+       public void appendNamedData(String name, byte[] data) {
+               NfcCardNamedData dataobj = new NfcCardNamedData();
+               dataobj.name = name;
+               dataobj.data = data;
+               namedDataRecords.add(dataobj);
+       }
+
+       public byte[] intToBytes(int my_int) throws IOException {
+               ByteArrayOutputStream bos = new ByteArrayOutputStream();
+               ObjectOutput out = new ObjectOutputStream(bos);
+               out.writeInt(my_int);
+               out.close();
+               byte[] int_bytes = bos.toByteArray();
+               bos.close();
+               return int_bytes;
+       }
+
+       private int bytesToInt(byte[] int_bytes) throws IOException {
+               ByteArrayInputStream bis = new ByteArrayInputStream(int_bytes);
+               ObjectInputStream ois = new ObjectInputStream(bis);
+               int my_int = ois.readInt();
+               ois.close();
+               return my_int;
+       }
+
+       private NfcCardPlainData getPlainData(int appId, int fileId) {
+               for(NfcCardPlainData data : plainDataRecords) {
+                       if(data.appid == appId && data.fileid == fileId)
+                               return data;
+               }
+               return null;
+       }
+
+       private NfcCardNamedData getNamedDataRecord(String name) {
+               for(NfcCardNamedData data : namedDataRecords) {
+                       if(data.name.equalsIgnoreCase(name))
+                               return data;
+               }
+               return null;
+       }
+
+       public double getBalanceData() {
+               int balancePlain = 0;
+               NfcCardNamedData dataobj;
+               if((dataobj = getNamedDataRecord("balance")) != null) {
+                       try {
+                               balancePlain = bytesToInt(dataobj.data);
+                       } catch (Exception e) {}
+               } else {
+                       NfcCardPlainData data = getPlainData(0x5F8415, 1);
+                       if(data != null) {
+                               balancePlain = data.bundle.getInt("data");
+                       }
+               }
+               return balancePlain / 1000.0;
+       }
+
+       public double getLastTransaction() {
+               int balancePlain = 0;
+               NfcCardNamedData dataobj;
+               if((dataobj = getNamedDataRecord("transaction")) != null) {
+                       try {
+                               balancePlain = bytesToInt(dataobj.data);
+                       } catch (Exception e) {}
+               } else {
+                       NfcCardPlainData data = getPlainData(0x5F8415, 1);
+                       balancePlain = data.bundle.getInt("value");
+               }
+               return balancePlain / 1000.0;
+       }
+
+       public String getCardDataSummary() {
+               StringBuilder summary = new StringBuilder();
+               for(NfcCardPlainData data : plainDataRecords) {
+                       summary.append("App: "+data.appid+"  File: "+data.fileid+"\n");
+                       Bundle bnd = data.bundle;
+                       if(bnd.containsKey("type"))
+                               summary.append("Type: "+bnd.getString("type")+"\n");
+                       if(bnd.containsKey("value"))
+                               summary.append("Value: "+bnd.getInt("value")+"  (Min: "+bnd.getInt("min")+"  Max: "+bnd.getInt("max")+"  Limited: "+bnd.getByte("limited")+")\n");
+                       if(bnd.containsKey("records"))
+                               summary.append("Records: "+bnd.getInt("records")+"  (Size: "+bnd.getInt("size")+"  Max: "+bnd.getInt("max")+")\n");
+                       else if(bnd.containsKey("size"))
+                               summary.append("Size: "+bnd.getInt("size")+"\n");
+
+                       if(bnd.containsKey("data"))
+                               summary.append("Data: "+bnd.getInt("data")+"\n");
+                       if(bnd.containsKey("file"))
+                               summary.append("File: "+bytesToHex(bnd.getByteArray("file"))+"\n");
+                       if(bnd.containsKey("record"))
+                               summary.append("Record: "+bytesToHex(bnd.getByteArray("record"))+"\n");
+               }
+               return summary.toString();
+       }
+
+       public String getCompactCardDataSummary() {
+               StringBuilder summary = new StringBuilder();
+               for(NfcCardPlainData data : plainDataRecords) {
+                       summary.append("{"+data.appid+", "+data.fileid);
+                       Bundle bnd = data.bundle;
+                       summary.append("} ");
+
+                       if(bnd.containsKey("value"))
+                               summary.append("[VAL "+bnd.getInt("value")+", >"+bnd.getInt("min")+", <"+bnd.getInt("max")+"] ");
+                       if(bnd.containsKey("records"))
+                               summary.append("[REC "+bnd.getInt("records")+", #"+bnd.getInt("size")+", <"+bnd.getInt("max")+"] ");
+                       else if(bnd.containsKey("size"))
+                               summary.append("[STD "+bnd.getInt("size")+"] ");
+
+                       if(bnd.containsKey("data"))
+                               summary.append(bnd.getInt("data") + " ");
+                       if(bnd.containsKey("file"))
+                               summary.append(bytesToHex(bnd.getByteArray("file")) + " ");
+                       if(bnd.containsKey("record"))
+                               summary.append(bytesToHex(bnd.getByteArray("record")) + " ");
+                       summary.append("\n");
+               }
+               return summary.toString();
+       }
+
+       public void setUniqueId(int uid) {
+               iUniqueCardId = uid;
+       }
+
+       public int getUniqueid() {
+               return iUniqueCardId;
+       }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/Readers.java b/app/src/main/java/de/dhbwloe/campusapp/nfcreader/cardreader/Readers.java
new file mode 100644 (file)
index 0000000..9bb2a35
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Readers.java
+ *
+ * Copyright (C) 2014 Jakob Wenzel
+ *
+ * Authors:
+ * Jakob Wenzel <jakobwenzel92@gmail.com>
+ *
+ * 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/>.
+ */
+
+package de.dhbwloe.campusapp.nfcreader.cardreader;
+
+import android.nfc.Tag;
+import android.nfc.tech.IsoDep;
+import android.util.Log;
+
+import com.codebutler.farebot.card.desfire.DesfireException;
+import com.codebutler.farebot.card.desfire.DesfireProtocol;
+
+import java.io.IOException;
+
+public class Readers implements ICardReader {
+       private static final String TAG = Readers.class.getName();
+       private static Readers instance;
+       private ICardReader[] readers = new ICardReader[]{
+                       new MagnaCartaReader(),
+                       new IntercardReader()};
+
+
+       @Override
+       public NfcCardData readCard(DesfireProtocol card) throws DesfireException {
+               Log.i(TAG, "Trying all readers");
+               for (ICardReader reader : readers) {
+                       Log.i(TAG, "Trying " + reader.getClass().getSimpleName());
+                       NfcCardData val = reader.readCard(card);
+                       if (val!=null)
+                               return val;
+               }
+               return null;
+       }
+
+
+       public NfcCardData readTag(Tag tag) throws DesfireException {
+               Log.i(TAG, "Loading tag");
+               IsoDep tech = IsoDep.get(tag);
+
+               try {
+                       tech.connect();
+               } catch (IOException e) {
+                       //Tag was removed. We fail silently.
+                       e.printStackTrace();
+                       return null;
+               }
+
+               try {
+                       DesfireProtocol desfireTag = new DesfireProtocol(tech);
+
+
+                       //Android has a Bug on Devices using a Broadcom NFC chip. See
+                       // http://code.google.com/p/android/issues/detail?id=58773
+                       //A Workaround is to connected to the tag, issue a dummy operation and then reconnect...
+                       try {
+                               desfireTag.selectApp(0);
+                       }catch (ArrayIndexOutOfBoundsException e) {
+                               //Exception occurs because the actual response is shorter than the error response
+                               Log.i(TAG, "Broadcom workaround was needed");
+                       }
+
+                       tech.close();
+                       tech.connect();
+
+                       NfcCardData val = Readers.getInstance().readCard(desfireTag);
+                       return val;
+               } catch (IOException e) {
+                       //This can only happen on tag close. we ignore this.
+                       e.printStackTrace();
+                       return null;
+               } finally {
+                       if (tech.isConnected())
+                               try {
+                                       tech.close();
+                               } catch (IOException e) {
+                                       e.printStackTrace();
+                               }
+               }
+
+       }
+
+       public static Readers getInstance() {
+               if (instance == null)
+                       instance = new Readers();
+               return instance;
+       }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/search/DhbwSearchHelper.java b/app/src/main/java/de/dhbwloe/campusapp/search/DhbwSearchHelper.java
new file mode 100644 (file)
index 0000000..0407017
--- /dev/null
@@ -0,0 +1,112 @@
+package de.dhbwloe.campusapp.search;
+
+import android.util.Log;
+
+import com.loopj.android.http.AsyncHttpClient;
+import com.loopj.android.http.AsyncHttpResponseHandler;
+import com.loopj.android.http.RequestParams;
+
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
+import cz.msebera.android.httpclient.Header;
+import de.dhbwloe.campusapp.fragments.AppSearchListItem;
+
+/**
+ * Created by pk910 on 25.01.2016.
+ */
+public class DhbwSearchHelper extends SearchHelper {
+
+    public DhbwSearchHelper() {
+        super(true);
+    }
+
+    @Override
+    protected void processWebRequest(AsyncHttpResponseHandler handler, String keywords) {
+        AsyncHttpClient client = new AsyncHttpClient();
+        String url = "https://www.dhbw-loerrach.de/suche.html?&L=0";
+        RequestParams params = new RequestParams();
+        /*  WHAT THE HELL DO THEY REQUIRE FOR THEIR STUPID SEARCH!?
+        tx_indexedsearch[_sections]:0
+        tx_indexedsearch[_freeIndexUid]:_
+        tx_indexedsearch[pointer]:0
+        tx_indexedsearch[ext]:1
+        tx_indexedsearch[defOp]:0
+        tx_indexedsearch[sword]:paintball
+        tx_indexedsearch[type]:1
+        tx_indexedsearch[defOp]:0
+        tx_indexedsearch[media]:-1
+        tx_indexedsearch[lang]:-1
+        tx_indexedsearch[sections]:0
+        tx_indexedsearch[order]:rank_flag
+        tx_indexedsearch[desc]:0
+        tx_indexedsearch[results]:10
+        tx_indexedsearch[group]:flat
+        tx_indexedsearch[extResume]:0
+        tx_indexedsearch[extResume]:1
+        tx_indexedsearch[submit_button]:Suche
+         */
+        params.put("tx_indexedsearch[_sections]","0");
+        params.put("tx_indexedsearch[_freeIndexUid]","_");
+        params.put("tx_indexedsearch[pointer]","0");
+        params.put("tx_indexedsearch[ext]","1");
+        params.put("tx_indexedsearch[defOp]","0");
+        params.put("tx_indexedsearch[sword]", keywords);
+        params.put("tx_indexedsearch[type]","1");
+        params.put("tx_indexedsearch[defOp]","0");
+        params.put("tx_indexedsearch[media]","-1");
+        params.put("tx_indexedsearch[lang]","-1");
+        params.put("tx_indexedsearch[sections]","0");
+        params.put("tx_indexedsearch[order]","rank_flag");
+        params.put("tx_indexedsearch[desc]","0");
+        params.put("tx_indexedsearch[results]","40");
+        params.put("tx_indexedsearch[group]","flat");
+        params.put("tx_indexedsearch[extResume]","0");
+        params.put("tx_indexedsearch[extResume]","1");
+        params.put("tx_indexedsearch[submit_button]","Suche");
+
+        Log.i("DhbwSearchHelper", "Search: "+keywords+" ("+url+")");
+        client.post(url, params, handler);
+    }
+
+    @Override
+    protected List<AppSearchListItem> onHtmlDocumentReceived(Document document) {
+        ArrayList<AppSearchListItem> results = new ArrayList<AppSearchListItem>();
+        Elements searchResultsTables = document.select(".tx-indexedsearch-res > table");
+        for(Element searchResultsTable : searchResultsTables) {
+            SearchTarget target = new SearchTarget();
+            target.setInAppTarget(true);
+            target.setTargetUrl("WebBrowser");
+
+            Elements titleLinkElement = searchResultsTable.select(".tx-indexedsearch-title > a");
+            Elements descriptionElement = searchResultsTable.select(".tx-indexedsearch-descr");
+            if(titleLinkElement.size() == 0 || titleLinkElement.size() == 0)
+                continue;
+            String title = titleLinkElement.get(0).text();
+            String link = titleLinkElement.get(0).attr("href");
+            if(!link.matches("^https?://.*"))
+                link = "https://www.dhbw-loerrach.de/" + link;
+
+            String description = descriptionElement.get(0).text();
+
+            target.setArgument("url", link);
+
+            AppSearchListItem result = new AppSearchListItem("DHBW: "+title, description, target);
+            results.add(result);
+        }
+        return results;
+    }
+
+    @Override
+    protected List<AppSearchListItem> onPlainTextReceived(byte[] data) {
+        return null;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/search/SearchHelper.java b/app/src/main/java/de/dhbwloe/campusapp/search/SearchHelper.java
new file mode 100644 (file)
index 0000000..a944113
--- /dev/null
@@ -0,0 +1,94 @@
+package de.dhbwloe.campusapp.search;
+
+import android.util.Log;
+
+import com.loopj.android.http.AsyncHttpResponseHandler;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+import cz.msebera.android.httpclient.Header;
+import de.dhbwloe.campusapp.fragments.AppSearchListItem;
+
+/**
+ * Created by pk910 on 25.01.2016.
+ */
+public abstract class SearchHelper {
+
+    protected boolean bRequestRunning = false;
+    protected boolean bHtmlParser;
+    protected SearchResultListener oRequestListener = null;
+
+    public SearchHelper(boolean htmlparser) {
+        bHtmlParser = htmlparser;
+    }
+
+    protected void performWebRequest(String keywords) {
+        AsyncHttpResponseHandler handler = new AsyncHttpResponseHandler() {
+            @Override
+            public void onStart() {
+            }
+            @Override
+            public void onSuccess(int statusCode, Header[] headers, byte[] response) {
+                boolean parsedHttp = false;
+                if(bHtmlParser) {
+                    try {
+                        Document document = Jsoup.parse(new String(response));
+
+                        parsedHttp = true;
+                        bRequestRunning = false;
+                        try {
+                            List<AppSearchListItem> results = onHtmlDocumentReceived(document);
+                            oRequestListener.onSearchResultsReceived(results);
+                        } catch(Exception e) {
+                            oRequestListener.onSearchFailed(e.getMessage());
+                        }
+                    } catch(Exception e) {
+                    }
+                }
+                if(!parsedHttp) {
+                    try {
+                        List<AppSearchListItem> results = onPlainTextReceived(response);
+                        oRequestListener.onSearchResultsReceived(results);
+                    } catch(Exception e) {
+                        oRequestListener.onSearchFailed(e.getMessage());
+                    }
+                }
+                bRequestRunning = false;
+            }
+            @Override
+            public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
+                String error = null;
+                try {
+                    error = new String(errorResponse, "US-ASCII");
+                } catch (Exception e1) {
+                }
+                Log.i("HTTPClient", "  Error: " + statusCode + " - " + error);
+                bRequestRunning = false;
+                oRequestListener.onSearchFailed(statusCode + ": " + error);
+            }
+            @Override
+            public void onRetry(int retryNo) {
+            }
+        };
+        processWebRequest(handler, keywords);
+    }
+
+    public void search(String keywords, SearchResultListener resultlistener) {
+        if(bRequestRunning) {
+            resultlistener.onSearchFailed("another request is already running!");
+            return;
+        }
+        oRequestListener = resultlistener;
+        bRequestRunning = true;
+
+        performWebRequest(keywords);
+    }
+
+    protected abstract void processWebRequest(AsyncHttpResponseHandler handler, String keywords);
+    protected abstract List<AppSearchListItem> onHtmlDocumentReceived(Document document);
+    protected abstract List<AppSearchListItem> onPlainTextReceived(byte[] data);
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/search/SearchIndices.java b/app/src/main/java/de/dhbwloe/campusapp/search/SearchIndices.java
new file mode 100644 (file)
index 0000000..fe1db98
--- /dev/null
@@ -0,0 +1,78 @@
+package de.dhbwloe.campusapp.search;
+
+import java.util.Date;
+
+/**
+ * Created by pk910 on 19.01.2016.
+ */
+public class SearchIndices {
+    private String keyname = null;
+    private StringBuffer keywordsBuffer = new StringBuffer();
+    protected boolean isStatic = false;
+    protected String target = new String();
+    protected String keywords = null;
+    protected String title = null;
+    protected String description = null;
+    protected long updateTime = 0;
+
+    public SearchIndices(String keyname) {
+        this.keyname = keyname;
+        this.updateTime = (new Date()).getTime()/1000;
+    }
+
+    public SearchIndices(String keyname, boolean isStatic) {
+        this.keyname = keyname;
+        this.isStatic = isStatic;
+    }
+
+    public void addKeyWord(String words) {
+        if(keywordsBuffer.length() > 0)
+            keywordsBuffer.append('\n');
+        keywordsBuffer.append(words);
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public void setUpdateTime(long updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getKeyName() {
+        return this.keyname;
+    }
+
+    public String getKeyWords() {
+        return (this.keywords != null ? this.keywords : "") + this.keywordsBuffer.toString();
+    }
+
+    public boolean getIsStatic() {
+        return this.isStatic;
+    }
+
+    public String getTarget() {
+        return this.target;
+    }
+
+    public String getDescription() {
+        return this.description;
+    }
+
+    public String getTitle() {
+        return this.title;
+    }
+
+    public long getUpdateTime() {
+        return this.updateTime;
+    }
+}
+
diff --git a/app/src/main/java/de/dhbwloe/campusapp/search/SearchResultListener.java b/app/src/main/java/de/dhbwloe/campusapp/search/SearchResultListener.java
new file mode 100644 (file)
index 0000000..72d5e2a
--- /dev/null
@@ -0,0 +1,15 @@
+package de.dhbwloe.campusapp.search;
+
+import java.util.List;
+
+import de.dhbwloe.campusapp.fragments.AppSearchListItem;
+
+/**
+ * Created by pk910 on 25.01.2016.
+ */
+public interface SearchResultListener {
+
+    public void onSearchResultsReceived(List<AppSearchListItem> results);
+    public void onSearchFailed(String error);
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/search/SearchTarget.java b/app/src/main/java/de/dhbwloe/campusapp/search/SearchTarget.java
new file mode 100644 (file)
index 0000000..a02466e
--- /dev/null
@@ -0,0 +1,74 @@
+package de.dhbwloe.campusapp.search;
+
+import android.os.Bundle;
+
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+import de.dhbwloe.campusapp.NavigationManager;
+
+/**
+ * Created by pk910 on 19.01.2016.
+ */
+public class SearchTarget {
+    private boolean bInAppTarget = false;
+    private String sTargetUrl = null;
+    private Bundle oArguments = new Bundle();
+
+    public SearchTarget(String target) {
+        if(target.startsWith("#")) { // InApp Navigation
+            bInAppTarget = true;
+            target = target.substring(1);
+        }
+        String[] parts = target.split("#", 2);
+        if(parts.length != 0)
+            sTargetUrl = parts[0];
+        if(parts.length > 1) {
+            // parse arguments?
+            String[] args = parts[1].split("&");
+            for(int i = 0; i < args.length; i++) {
+                String[] arg = args[i].split("=");
+                try {
+                    if (arg.length == 2)
+                        oArguments.putString(URLDecoder.decode(arg[0], "UTF-8"), URLDecoder.decode(arg[1], "UTF-8"));
+                    else
+                        oArguments.putBoolean(URLDecoder.decode(arg[0], "UTF-8"), true);
+                } catch(Exception e) {}
+            }
+        }
+
+    }
+
+    public SearchTarget() {
+    }
+
+    public boolean isInAppTarget() {
+        return bInAppTarget;
+    }
+
+    public void setInAppTarget(boolean inAppTarget) {
+        bInAppTarget = inAppTarget;
+    }
+
+    public String getTargetUrl() {
+        return sTargetUrl;
+    }
+
+    public void setTargetUrl(String url) {
+        sTargetUrl = url;
+    }
+
+    public void setArgument(String name, String value) {
+        oArguments.putString(name, value);
+    }
+
+    public void navigate(NavigationManager navigationManager) {
+        Bundle args = oArguments;
+        if(bInAppTarget)
+            navigationManager.navigatePage(sTargetUrl, args);
+        else {
+            args.putString("url", sTargetUrl);
+            navigationManager.navigatePage("WebBrowser", args, false);
+        }
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/search/StuvSearchHelper.java b/app/src/main/java/de/dhbwloe/campusapp/search/StuvSearchHelper.java
new file mode 100644 (file)
index 0000000..8a635ce
--- /dev/null
@@ -0,0 +1,79 @@
+package de.dhbwloe.campusapp.search;
+
+import android.util.Xml;
+
+import com.loopj.android.http.AsyncHttpClient;
+import com.loopj.android.http.AsyncHttpResponseHandler;
+
+import org.jsoup.nodes.Document;
+
+import java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
+import de.dhbwloe.campusapp.fragments.AppSearchListItem;
+import de.dhbwloe.campusapp.network.XmlEntry;
+import de.dhbwloe.campusapp.network.XmlRequestHelper;
+
+/**
+ * Created by pk910 on 25.01.2016.
+ */
+public class StuvSearchHelper extends SearchHelper {
+
+    public StuvSearchHelper() {
+        super(false);
+
+    }
+
+    @Override
+    protected void processWebRequest(AsyncHttpResponseHandler handler, String keywords) {
+        AsyncHttpClient client = new AsyncHttpClient();
+        String url = null;
+        try {
+            url = "http://stuv-loerrach.de/feed/?s="+ URLEncoder.encode(keywords, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+        }
+        client.get(url, null, handler);
+    }
+
+    @Override
+    protected List<AppSearchListItem> onHtmlDocumentReceived(Document document) {
+        //unused in here
+        return null;
+    }
+
+    @Override
+    protected List<AppSearchListItem> onPlainTextReceived(byte[] data) {
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        List<XmlEntry> entries = XmlRequestHelper.parseXml(inputStream, "channel", "item");
+        ArrayList<AppSearchListItem> results = new ArrayList<>();
+
+        for(XmlEntry entry : entries) {
+            XmlEntry element;
+
+            SearchTarget target = new SearchTarget();
+            target.setInAppTarget(true);
+            target.setTargetUrl("WebBrowser");
+
+            element = XmlEntry.FindXmlEntryByName(entry, "title");
+            String title = (element == null ? null : element.getValue());
+
+            element = XmlEntry.FindXmlEntryByName(entry, "link");
+            String link = (element == null ? null : element.getValue());
+
+            element = XmlEntry.FindXmlEntryByName(entry, "description");
+            String description = (element == null ? null : element.getValue());
+
+            target.setArgument("url", link);
+
+            AppSearchListItem result = new AppSearchListItem("STUV: "+title, description, target);
+            results.add(result);
+
+        }
+
+        return results;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/vorlesungen/CourseEvent.java b/app/src/main/java/de/dhbwloe/campusapp/vorlesungen/CourseEvent.java
new file mode 100644 (file)
index 0000000..71a49ca
--- /dev/null
@@ -0,0 +1,167 @@
+package de.dhbwloe.campusapp.vorlesungen;
+
+import de.dhbwloe.campusapp.database.DatabaseManager;
+
+/**
+ * Created by pk910 on 20.01.2016.
+ */
+public class CourseEvent {
+    private int iEventId;
+    private String sCourseName;
+    private String sUniqueId;
+    private int iSequenceId;
+    private long iEventFrom, iEventTo;
+    private String sEventTitle, sEventLocation, sEventStatus;
+    private String sRecurRule, sExcludeDates;
+    private CourseGroup oCourseGroup;
+
+    private boolean bMustUpdate = false;
+    private boolean bIsNew = false;
+
+    public CourseEvent(int id, String coursename, String uniqueid, int sequenceid, long eventfrom, long eventto, String title, String location, String status, String rrule, String exdates, CourseGroup group) {
+        iEventId = id;
+        sCourseName = coursename;
+        sUniqueId = uniqueid;
+        iSequenceId = sequenceid;
+        iEventFrom = eventfrom;
+        iEventTo = eventto;
+        sEventTitle = title;
+        sEventLocation = location;
+        sEventStatus = status;
+        sRecurRule = rrule;
+        sExcludeDates = exdates;
+
+        oCourseGroup = group;
+        if(group != null)
+            group.addCourseEvent(this);
+    }
+
+    public CourseEvent(String coursename, String uniqueid, int sequenceid, boolean isNew) {
+        sCourseName = coursename;
+        sUniqueId = uniqueid;
+        iSequenceId = sequenceid;
+        bIsNew = isNew;
+        bMustUpdate = isNew;
+    }
+
+    public void setEventId(int id) {
+        iEventId = id;
+    }
+
+    public int getEventId() {
+        return iEventId;
+    }
+
+    private void resetUpdateFlag() {
+        bIsNew = false;
+        bMustUpdate = false;
+    }
+
+    public void update(DatabaseManager dbm) {
+        if(!bMustUpdate)
+            return;
+
+        dbm.updateCourseCalendar(this);
+        resetUpdateFlag();
+    }
+
+    public CourseGroup getCourseGroup() {
+        return oCourseGroup;
+    }
+
+    public void setCourseGroup(CourseGroup group) {
+        group.addCourseEvent(this);
+        oCourseGroup = group;
+    }
+
+    public String getUniqueId() {
+        return sUniqueId;
+    }
+
+    public boolean IsPendingUpdate() {
+        return bMustUpdate;
+    }
+
+    public boolean IsNewEvent() {
+        return bIsNew;
+    }
+
+    public String getEventStatus() {
+        return sEventStatus;
+    }
+
+    public void setEventStatus(String sEventStatus) {
+        this.sEventStatus = sEventStatus;
+        bMustUpdate = true;
+    }
+
+    public String getEventLocation() {
+        return sEventLocation;
+    }
+
+    public void setEventLocation(String sEventLocation) {
+        this.sEventLocation = sEventLocation;
+        bMustUpdate = true;
+    }
+
+    public String getEventTitle() {
+        return sEventTitle;
+    }
+    public String getGroupTitle() {
+        return sEventTitle; // maybe cut prof name?
+    }
+
+    public void setEventTitle(String sEventTitle) {
+        this.sEventTitle = sEventTitle;
+        bMustUpdate = true;
+    }
+
+    public long getEventTo() {
+        return iEventTo;
+    }
+
+    public void setEventTo(long iEventTo) {
+        this.iEventTo = iEventTo;
+        bMustUpdate = true;
+    }
+
+    public long getEventFrom() {
+        return iEventFrom;
+    }
+
+    public void setEventFrom(long iEventFrom) {
+        this.iEventFrom = iEventFrom;
+        bMustUpdate = true;
+    }
+
+    public String getCourseName() {
+        return sCourseName;
+    }
+
+    public int getSequenceId() {
+        return iSequenceId;
+    }
+
+    public void setSequenceId(int iSequenceId) {
+        this.iSequenceId = iSequenceId;
+        bMustUpdate = true;
+    }
+
+    public String getRecurRule() {
+        return sRecurRule;
+    }
+
+    public void setRecurRule(String rrule) {
+        this.sRecurRule = rrule;
+        bMustUpdate = true;
+    }
+
+    public String getExcludeDates() {
+        return sExcludeDates;
+    }
+
+    public void setExcludeDates(String exrules) {
+        this.sExcludeDates = exrules;
+        bMustUpdate = true;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/vorlesungen/CourseGroup.java b/app/src/main/java/de/dhbwloe/campusapp/vorlesungen/CourseGroup.java
new file mode 100644 (file)
index 0000000..06475c1
--- /dev/null
@@ -0,0 +1,75 @@
+package de.dhbwloe.campusapp.vorlesungen;
+
+import android.provider.ContactsContract;
+
+import java.util.ArrayList;
+
+import de.dhbwloe.campusapp.database.DatabaseManager;
+
+/**
+ * Created by pk910 on 21.01.2016.
+ */
+public class CourseGroup {
+    private static ArrayList<CourseGroup> CourseGroups = new ArrayList<CourseGroup>();
+    private int iCourseGroupId;
+    private String sCourseGroupName;
+    private String sCourseName;
+    private boolean bIsNew = false;
+    private ArrayList<CourseEvent> events = new ArrayList<CourseEvent>();
+
+    public static CourseGroup GetCourseGroupById(DatabaseManager dbm, int id) {
+        for(CourseGroup group : CourseGroups) {
+            if(group.iCourseGroupId == id)
+                return group;
+        }
+        return dbm.getCourseGroup(id);
+    }
+
+    public static CourseGroup GetCourseGroupByName(DatabaseManager dbm, String coursename, String groupname) {
+        for(CourseGroup group : CourseGroups) {
+            if(group.sCourseName.equalsIgnoreCase(coursename) && group.sCourseGroupName.equalsIgnoreCase(groupname))
+                return group;
+        }
+        CourseGroup group = dbm.getCourseGroup(coursename, groupname);
+        if(group == null) {
+            group = dbm.addCourseGroup(coursename, groupname);
+            group.bIsNew = true;
+        }
+        return group;
+    }
+
+    public static void ResetEventGroups() {
+        for(CourseGroup group : CourseGroups) {
+            group.events.clear();
+        }
+    }
+
+
+    public CourseGroup(int id, String coursename, String groupname) {
+        iCourseGroupId = id;
+        sCourseGroupName = groupname;
+        sCourseName = coursename;
+
+        CourseGroups.add(this);
+    }
+
+    public void addCourseEvent(CourseEvent event) {
+        this.events.add(event);
+    }
+
+    public CourseEvent[] getCourseEvents() {
+        return (CourseEvent[])this.events.toArray();
+    }
+
+    public int getGroupId() {
+        return iCourseGroupId;
+    }
+
+    public boolean isNewGroup(boolean reset) {
+        boolean ret = bIsNew;
+        if(reset)
+            bIsNew = false;
+        return ret;
+    }
+
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/vorlesungen/VorlesungsplanManager.java b/app/src/main/java/de/dhbwloe/campusapp/vorlesungen/VorlesungsplanManager.java
new file mode 100644 (file)
index 0000000..5c68a4f
--- /dev/null
@@ -0,0 +1,261 @@
+package de.dhbwloe.campusapp.vorlesungen;
+
+
+import android.util.Log;
+
+import net.fortuna.ical4j.model.Calendar;
+import net.fortuna.ical4j.model.Component;
+import net.fortuna.ical4j.model.Property;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.ListIterator;
+import java.util.Locale;
+
+import de.dhbwloe.campusapp.CampusAppContext;
+import de.dhbwloe.campusapp.network.IscRequestHelper;
+import de.dhbwloe.campusapp.search.SearchIndices;
+
+/**
+ * Created by pk910 on 19.01.2016.
+ */
+public class VorlesungsplanManager extends IscRequestHelper {
+    private static final String[][] PLAN_SOURCES = {
+            {"STUV", "https://www.google.com/calendar/ical/asta.dhbw.de_c0g35t6hrh16kr4ankrqg2rdm4%40group.calendar.google.com/public/basic.ics"},
+    };
+
+    private CampusAppContext AppContext;
+    private boolean bRequestRunning = false;
+    private boolean bFastSynchronisation = false;
+    private String sCourseName;
+    private String source[];
+    private ArrayList<VorlesungsplanManagerInterface> aCallbackInterfaces = new ArrayList<VorlesungsplanManagerInterface>();
+
+    public VorlesungsplanManager(CampusAppContext context, String courseName) {
+        AppContext = context;
+        sCourseName = courseName;
+
+        for(String src[] : PLAN_SOURCES) {
+            if(src[0].equalsIgnoreCase(courseName)) {
+                source = src;
+                break;
+            }
+        }
+    }
+
+    public void performFastSynchronisation(VorlesungsplanManagerInterface callback) {
+        performSynchronisation(callback, false);
+    }
+
+    public void performFullSynchronisation(VorlesungsplanManagerInterface callback) {
+        performSynchronisation(callback, true);
+    }
+
+    private void performSynchronisation(VorlesungsplanManagerInterface callback, boolean fullsync) {
+        if(sCourseName.length() == 0) {
+            callback.onVorlesungsplanUpdateFail("no course name");
+            return;
+        }
+
+        aCallbackInterfaces.add(callback);
+        if(bRequestRunning)
+            return;
+
+        bFastSynchronisation = !fullsync;
+        bRequestRunning = true;
+        String courseCalendarUrl;
+        if(source == null) {
+            courseCalendarUrl = "https://webmail.dhbw-loerrach.de/owa/calendar/kal-" + sCourseName + "@dhbw-loerrach.de/Kalender/calendar.ics";
+        } else {
+            courseCalendarUrl = source[1];
+        }
+        requestCalenderFromWeb(courseCalendarUrl);
+    }
+
+    @Override
+    protected void onCalendarReceived(Calendar calendar) {
+        long timeFrom, timeTo;
+        long now = (new Date()).getTime() / 1000;
+        if(bFastSynchronisation) {
+            timeFrom = now - (86400 * 5);
+            timeTo = now + (86400 * 7 * 4);
+        } else {
+            timeFrom = now - (86400 * 365 * 3);
+            timeTo = now + (86400 * 365 * 4);
+        }
+        CourseEvent[] events = AppContext.getDatabaseManager().getCourseCalendarEvents(sCourseName, timeFrom, timeTo);
+        Log.i("VPMSync", "Event count: " + events.length);
+        ArrayList<CourseEvent> newEvents = new ArrayList<CourseEvent>();
+        ArrayList<SearchIndices> newIndices = new ArrayList<SearchIndices>();
+        int lastEventIndex = 0;
+        ListIterator cIterator = calendar.getComponents().listIterator();
+
+        while (cIterator.hasNext()) {
+            Component event = (Component)cIterator.next();
+            CourseEvent dbEvent;
+            Property prop;
+
+            prop = event.getProperty("UID");
+            String uid;
+            if(prop != null)
+                uid = prop.getValue();
+            else
+                continue;
+
+            prop = event.getProperty("SEQUENCE");
+            int sequence;
+            if(prop != null)
+                sequence = Integer.parseInt(prop.getValue());
+            else
+                sequence = 1;
+
+            long startTime = 0, endTime = 0;
+            prop = event.getProperty("DTSTART");
+            if(prop != null)
+                startTime = getTimeFromTimestamp(prop.getValue());
+            else
+                Log.i("VPMSync", "Parse Event: DTSTART not found!");
+
+            prop = event.getProperty("DTEND");
+            if(prop != null)
+                endTime = getTimeFromTimestamp(prop.getValue());
+            else
+                Log.i("VPMSync", "Parse Event: DTEND not found!");
+
+            if(!(endTime > timeFrom && startTime < timeTo)) {
+                Log.i("VPMSync", "Skip Entry: ("+timeFrom+" - "+timeTo+")   Filter: "+startTime+" - "+endTime);
+                continue;
+            }
+
+            dbEvent = null;
+            for(int i = lastEventIndex; i < events.length; i++) {
+                if(events[i].getUniqueId().equalsIgnoreCase(uid)) {
+                    dbEvent = events[i];
+                    break;
+                }
+            }
+            if(dbEvent == null && lastEventIndex > 0) {
+                for(int i = 0; i < lastEventIndex; i++) {
+                    if(events[i].getUniqueId().equalsIgnoreCase(uid)) {
+                        dbEvent = events[i];
+                        break;
+                    }
+                }
+            }
+            if(dbEvent == null) {
+                for(CourseEvent cevent : newEvents) {
+                    if(cevent.getUniqueId().equalsIgnoreCase(uid)) {
+                        dbEvent = cevent;
+                        break;
+                    }
+                }
+            }
+
+            if(dbEvent == null) {
+                // new event!
+                Log.i("VLPSync", "New Event "+uid);
+                dbEvent = new CourseEvent(sCourseName, uid, sequence, true);
+                newEvents.add(dbEvent);
+            } else {
+                Log.i("VLPSync", "Existing Event "+uid+" ("+dbEvent.getSequenceId()+" >= "+sequence+")");
+                if(dbEvent.getSequenceId() >= sequence) {
+                    continue; // skip event
+                }
+                dbEvent.setSequenceId(sequence);
+            }
+            // perform update
+            prop = event.getProperty("SUMMARY");
+            if(prop != null)
+                dbEvent.setEventTitle(prop.getValue());
+            else
+                Log.i("VPMSync", "Parse Event: SUMMARY not found!");
+
+            dbEvent.setEventFrom(startTime);
+            dbEvent.setEventTo(endTime);
+
+            prop = event.getProperty("LOCATION");
+            if(prop != null)
+                dbEvent.setEventLocation(prop.getValue());
+            else
+                Log.i("VPMSync", "Parse Event: LOCATION not found!");
+
+            prop = event.getProperty("STATUS");
+            if(prop != null)
+                dbEvent.setEventStatus(prop.getValue());
+            else
+                Log.i("VPMSync", "Parse Event: STATUS not found!");
+
+            prop = event.getProperty("RRULE");
+            if(prop != null)
+                dbEvent.setRecurRule(prop.getValue());
+            else
+                dbEvent.setRecurRule("");
+
+            prop = event.getProperty("EXDATE");
+            if(prop != null)
+                dbEvent.setExcludeDates(prop.getValue());
+            else
+                dbEvent.setExcludeDates("");
+
+            CourseGroup group = CourseGroup.GetCourseGroupByName(AppContext.getDatabaseManager(), sCourseName, dbEvent.getGroupTitle().trim());
+            if(group != null)
+                dbEvent.setCourseGroup(group);
+
+            if(group.isNewGroup(true)) {
+                SearchIndices indices = new SearchIndices("Vorlesungsplan#Group"+group.getGroupId(), false);
+                indices.setUpdateTime(dbEvent.getEventFrom());
+                indices.setTarget("#Vorlesungsplan#groupid=" + group.getGroupId());
+                indices.setTitle("Vorlesungsplan " + dbEvent.getCourseName());
+                indices.setDescription("Vorlesung " + dbEvent.getEventTitle());
+                indices.addKeyWord(dbEvent.getGroupTitle());
+                indices.addKeyWord(dbEvent.getEventLocation());
+                newIndices.add(indices);
+            }
+
+            dbEvent.update(AppContext.getDatabaseManager());
+            Log.i("VPMSync", "Update Event: "+dbEvent.getUniqueId());
+        }
+
+        SearchIndices[] newIndicesArr = new SearchIndices[newIndices.size()];
+        newIndicesArr = newIndices.toArray(newIndicesArr);
+        AppContext.addSearchIndices(newIndicesArr);
+
+        for(VorlesungsplanManagerInterface callback : aCallbackInterfaces) {
+            callback.onVorlesungsplanUpdateDone();
+        }
+        aCallbackInterfaces.clear();
+        bRequestRunning = false;
+    }
+
+    private long getTimeFromTimestamp(String timestamp) {
+        // 20160425T090000
+        // 20160321
+        DateFormat df = new SimpleDateFormat("yyyyMMdd'T'kkmmss", Locale.ENGLISH);
+        try {
+            Date result =  df.parse(timestamp);
+            return result.getTime()/1000;
+        } catch (ParseException e) {
+            df = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
+            try {
+                Date result =  df.parse(timestamp);
+                return result.getTime()/1000;
+            } catch (ParseException e2) {
+                Log.i("VPMSync", "Failed parsing: "+timestamp);
+                return 0;
+            }
+        }
+    }
+
+    @Override
+    protected void onCalendarRequestFail(int statusCode, String errorMessage) {
+        Log.i("VPM", "Calendar Error: "+statusCode);
+        for(VorlesungsplanManagerInterface callback : aCallbackInterfaces) {
+            callback.onVorlesungsplanUpdateFail("error "+statusCode+": "+errorMessage);
+        }
+        aCallbackInterfaces.clear();
+        bRequestRunning = false;
+    }
+}
diff --git a/app/src/main/java/de/dhbwloe/campusapp/vorlesungen/VorlesungsplanManagerInterface.java b/app/src/main/java/de/dhbwloe/campusapp/vorlesungen/VorlesungsplanManagerInterface.java
new file mode 100644 (file)
index 0000000..5635bb8
--- /dev/null
@@ -0,0 +1,11 @@
+package de.dhbwloe.campusapp.vorlesungen;
+
+/**
+ * Created by pk910 on 20.01.2016.
+ */
+public interface VorlesungsplanManagerInterface {
+
+    public void onVorlesungsplanUpdateDone();
+    public void onVorlesungsplanUpdateFail(String errorMessage);
+
+}
diff --git a/app/src/main/res/drawable-hdpi/ic_info_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_info_black_24dp.png
new file mode 100644 (file)
index 0000000..da56077
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_info_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png
new file mode 100644 (file)
index 0000000..e200012
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png
new file mode 100644 (file)
index 0000000..a5ebdbd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_info_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_info_black_24dp.png
new file mode 100644 (file)
index 0000000..5ef3dc0
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_info_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png
new file mode 100644 (file)
index 0000000..b36475d
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png
new file mode 100644 (file)
index 0000000..9685e8e
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png differ
diff --git a/app/src/main/res/drawable-v21/ic_info_black_24dp.xml b/app/src/main/res/drawable-v21/ic_info_black_24dp.xml
new file mode 100644 (file)
index 0000000..34b8202
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
+</vector>
diff --git a/app/src/main/res/drawable-v21/ic_menu_camera.xml b/app/src/main/res/drawable-v21/ic_menu_camera.xml
new file mode 100644 (file)
index 0000000..0d9ea10
--- /dev/null
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
+</vector>
diff --git a/app/src/main/res/drawable-v21/ic_menu_gallery.xml b/app/src/main/res/drawable-v21/ic_menu_gallery.xml
new file mode 100644 (file)
index 0000000..f6872c4
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z" />
+</vector>
diff --git a/app/src/main/res/drawable-v21/ic_menu_manage.xml b/app/src/main/res/drawable-v21/ic_menu_manage.xml
new file mode 100644 (file)
index 0000000..c1be60b
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v21/ic_menu_send.xml b/app/src/main/res/drawable-v21/ic_menu_send.xml
new file mode 100644 (file)
index 0000000..00c668c
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z" />
+</vector>
diff --git a/app/src/main/res/drawable-v21/ic_menu_share.xml b/app/src/main/res/drawable-v21/ic_menu_share.xml
new file mode 100644 (file)
index 0000000..a28fb9e
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
+</vector>
diff --git a/app/src/main/res/drawable-v21/ic_menu_slideshow.xml b/app/src/main/res/drawable-v21/ic_menu_slideshow.xml
new file mode 100644 (file)
index 0000000..209aa64
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z" />
+</vector>
diff --git a/app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml b/app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml
new file mode 100644 (file)
index 0000000..e3400cf
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z" />
+</vector>
diff --git a/app/src/main/res/drawable-v21/ic_sync_black_24dp.xml b/app/src/main/res/drawable-v21/ic_sync_black_24dp.xml
new file mode 100644 (file)
index 0000000..3f0ac1c
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.0dp"
+    android:height="24.0dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png
new file mode 100644 (file)
index 0000000..46ed12a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png
new file mode 100644 (file)
index 0000000..7de8581
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png
new file mode 100644 (file)
index 0000000..860a5db
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png
new file mode 100644 (file)
index 0000000..a81eeb9
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png
new file mode 100644 (file)
index 0000000..ab8a9c4
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png
new file mode 100644 (file)
index 0000000..f799008
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png
new file mode 100644 (file)
index 0000000..c8f86b9
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png
new file mode 100644 (file)
index 0000000..86f89d7
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png
new file mode 100644 (file)
index 0000000..b9f56f3
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png differ
diff --git a/app/src/main/res/drawable/appsearch_resulttype_external.xml b/app/src/main/res/drawable/appsearch_resulttype_external.xml
new file mode 100644 (file)
index 0000000..607178a
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,8V4l8,8 -8,8v-4H4V8z"/>
+</vector>
diff --git a/app/src/main/res/drawable/appsearch_resulttype_inapp.xml b/app/src/main/res/drawable/appsearch_resulttype_inapp.xml
new file mode 100644 (file)
index 0000000..50c70b3
--- /dev/null
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"/>
+</vector>
diff --git a/app/src/main/res/drawable/dhbw_campus.jpg b/app/src/main/res/drawable/dhbw_campus.jpg
new file mode 100644 (file)
index 0000000..fa3cd9d
Binary files /dev/null and b/app/src/main/res/drawable/dhbw_campus.jpg differ
diff --git a/app/src/main/res/drawable/dhbw_campus_hd.jpg b/app/src/main/res/drawable/dhbw_campus_hd.jpg
new file mode 100644 (file)
index 0000000..8bbb69b
Binary files /dev/null and b/app/src/main/res/drawable/dhbw_campus_hd.jpg differ
diff --git a/app/src/main/res/drawable/dhbw_logo.png b/app/src/main/res/drawable/dhbw_logo.png
new file mode 100644 (file)
index 0000000..1985e06
Binary files /dev/null and b/app/src/main/res/drawable/dhbw_logo.png differ
diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml
new file mode 100644 (file)
index 0000000..08f7756
--- /dev/null
@@ -0,0 +1,8 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:angle="135"
+        android:endColor="#A30000"
+        android:startColor="#D9D9D9"
+        android:type="linear" />
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_campus_app.xml b/app/src/main/res/layout/activity_campus_app.xml
new file mode 100644 (file)
index 0000000..dc45769
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/drawer_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:openDrawer="start">
+
+    <include
+        layout="@layout/app_bar_campus_app"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <android.support.design.widget.NavigationView
+        android:id="@+id/nav_view"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:fitsSystemWindows="true"
+        app:headerLayout="@layout/nav_header_campus_app"
+        app:menu="@menu/activity_campus_app_drawer" />
+
+</android.support.v4.widget.DrawerLayout>
diff --git a/app/src/main/res/layout/app_bar_campus_app.xml b/app/src/main/res/layout/app_bar_campus_app.xml
new file mode 100644 (file)
index 0000000..617c9a2
--- /dev/null
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context="de.dhbwloe.campusapp.CampusApp">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay">
+
+            <LinearLayout
+                android:id="@+id/search_container"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:visibility="gone">
+
+                <EditText
+                    android:id="@+id/search_input"
+                    android:layout_width="0dp"
+                    android:layout_height="?attr/actionBarSize"
+                    android:layout_weight="1"
+                    android:background="@android:color/transparent"
+                    android:gravity="center_vertical"
+                    android:hint="Search"
+                    android:imeOptions="actionSearch"
+                    android:inputType="text"
+                    android:maxLines="1"
+                    android:paddingLeft="2dp"
+                    android:singleLine="true"
+                    android:textColor="#ffffff"
+                    android:textColorHint="#b3ffffff" />
+
+                <ImageView
+                    android:id="@+id/search_clear"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:src="@mipmap/ic_close_white"
+                    android:padding="16dp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/title_container"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:visibility="visible">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textAppearance="?android:attr/textAppearanceLarge"
+                    android:text="{Title}"
+                    android:layout_weight="1"
+                    android:imeOptions="actionSearch"
+                    android:id="@+id/title" />
+
+                <ImageView
+                    android:id="@+id/search_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@mipmap/ic_search"
+                    android:padding="16dp" />
+            </LinearLayout>
+        </android.support.v7.widget.Toolbar>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_campus_app" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/content_campus_app.xml b/app/src/main/res/layout/content_campus_app.xml
new file mode 100644 (file)
index 0000000..8101d31
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:context="de.dhbwloe.campusapp.CampusApp"
+    tools:showIn="@layout/app_bar_campus_app">
+
+    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+         />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/content_popup.xml b/app/src/main/res/layout/content_popup.xml
new file mode 100644 (file)
index 0000000..e89dc38
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/content_container"
+        android:background="@android:color/background_light"></RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_appsearch.xml b/app/src/main/res/layout/fragment_appsearch.xml
new file mode 100644 (file)
index 0000000..db3775d
--- /dev/null
@@ -0,0 +1,24 @@
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.design.widget.TabLayout
+            android:id="@+id/tabs"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:tabMode="scrollable"
+            />
+    </android.support.design.widget.AppBarLayout>
+
+    <android.support.v4.view.ViewPager
+        android:id="@+id/viewpager"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"  />
+</android.support.design.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/fragment_appsearch_list.xml b/app/src/main/res/layout/fragment_appsearch_list.xml
new file mode 100644 (file)
index 0000000..ee1c433
--- /dev/null
@@ -0,0 +1,13 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".fragments.AppSearch">
+
+    <ListView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/searchResultItems"
+        android:layout_gravity="bottom|left|right|top" />
+</LinearLayout>
diff --git a/app/src/main/res/layout/fragment_appsearch_listitem.xml b/app/src/main/res/layout/fragment_appsearch_listitem.xml
new file mode 100644 (file)
index 0000000..f16fb83
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="5dp"
+        android:focusable="false"
+        android:clickable="false">
+
+        <RelativeLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="3dp">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:text="{Title}"
+                android:id="@+id/resultTitle" />
+        </RelativeLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal">
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent">
+
+                <ImageView
+                    android:layout_width="40dp"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/imageView2"
+                    android:src="@drawable/appsearch_resulttype_inapp"
+                    android:adjustViewBounds="true" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="left"
+                android:layout_weight="1">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="{Description}"
+                    android:id="@+id/resultDescription" />
+            </LinearLayout>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
new file mode 100644 (file)
index 0000000..d6f19a5
--- /dev/null
@@ -0,0 +1,9 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".fragments.Dashboard">
+
+    <!-- TODO: Update blank fragment layout -->
+
+</FrameLayout>
diff --git a/app/src/main/res/layout/fragment_first_run.xml b/app/src/main/res/layout/fragment_first_run.xml
new file mode 100644 (file)
index 0000000..8596249
--- /dev/null
@@ -0,0 +1,20 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="de.dhbwloe.campusapp.fragments.FirstRun">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="{FirstRun}" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Start App"
+        android:id="@+id/startAppBtn"
+        android:layout_gravity="center_horizontal" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/fragment_impressum.xml b/app/src/main/res/layout/fragment_impressum.xml
new file mode 100644 (file)
index 0000000..4921639
--- /dev/null
@@ -0,0 +1,13 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="de.dhbwloe.campusapp.fragments.Impressum">
+
+    <!-- TODO: Update blank fragment layout -->
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:text="{Impressum}" />
+
+</FrameLayout>
diff --git a/app/src/main/res/layout/fragment_mensa.xml b/app/src/main/res/layout/fragment_mensa.xml
new file mode 100644 (file)
index 0000000..db3775d
--- /dev/null
@@ -0,0 +1,24 @@
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.design.widget.TabLayout
+            android:id="@+id/tabs"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:tabMode="scrollable"
+            />
+    </android.support.design.widget.AppBarLayout>
+
+    <android.support.v4.view.ViewPager
+        android:id="@+id/viewpager"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"  />
+</android.support.design.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/fragment_mensa_card.xml b/app/src/main/res/layout/fragment_mensa_card.xml
new file mode 100644 (file)
index 0000000..5cf1a46
--- /dev/null
@@ -0,0 +1,31 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    tools:context="de.dhbwloe.campusapp.fragments.MensaCard"
+    android:orientation="vertical">
+
+    <!-- TODO: Update blank fragment layout -->
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="@string/mensacard_guthaben"
+        android:id="@+id/textView10"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="20dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="{balance}"
+        android:id="@+id/balanceTxt"
+        android:layout_alignParentTop="false"
+        android:layout_centerHorizontal="true"
+        android:layout_below="@+id/textView10"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="60dp" />
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_mensa_tagesplan.xml b/app/src/main/res/layout/fragment_mensa_tagesplan.xml
new file mode 100644 (file)
index 0000000..74c1e17
--- /dev/null
@@ -0,0 +1,15 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="de.dhbwloe.campusapp.fragments.MensaTagesplan">
+
+    <!-- TODO: Update blank fragment layout -->
+
+    <ListView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/tagesplanItems"
+        android:layout_gravity="bottom|left|right|top" />
+</LinearLayout>
diff --git a/app/src/main/res/layout/fragment_mensa_tagesplan_listitem.xml b/app/src/main/res/layout/fragment_mensa_tagesplan_listitem.xml
new file mode 100644 (file)
index 0000000..fe70e1c
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="{menuName}"
+        android:id="@+id/txtMenueName"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="{name}"
+        android:id="@+id/txtName"
+        android:layout_below="@+id/txtMenueName"
+        android:layout_alignLeft="@+id/txtMenueName"
+        android:layout_alignStart="@+id/txtMenueName"
+        android:layout_marginLeft="30dp"
+        android:layout_marginStart="31dp"
+        android:layout_marginTop="8dp" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_mensa_wochenplan.xml b/app/src/main/res/layout/fragment_mensa_wochenplan.xml
new file mode 100644 (file)
index 0000000..5e4cdd7
--- /dev/null
@@ -0,0 +1,13 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="de.dhbwloe.campusapp.fragments.MensaWochenplan">
+
+    <!-- TODO: Update blank fragment layout -->
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:text="{MensaWochenplan}" />
+
+</FrameLayout>
diff --git a/app/src/main/res/layout/fragment_news.xml b/app/src/main/res/layout/fragment_news.xml
new file mode 100644 (file)
index 0000000..a4d4419
--- /dev/null
@@ -0,0 +1,13 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="de.dhbwloe.campusapp.fragments.News">
+
+    <!-- TODO: Update blank fragment layout -->
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:text="{News}" />
+
+</FrameLayout>
diff --git a/app/src/main/res/layout/fragment_splashscreen.xml b/app/src/main/res/layout/fragment_splashscreen.xml
new file mode 100644 (file)
index 0000000..d80dfcb
--- /dev/null
@@ -0,0 +1,45 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".fragments.SplashScreen">
+
+    <!-- TODO: Update blank fragment layout -->
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/splashImage"
+        android:layout_centerVertical="true"
+        android:layout_centerHorizontal="true"
+        android:src="@drawable/dhbw_campus_hd"
+        android:scaleType="centerCrop" />
+
+    <ProgressBar
+        android:layout_width="match_parent"
+        android:layout_height="20dp"
+        android:id="@+id/splashProgress"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentEnd="true"
+        style="?android:attr/progressBarStyleHorizontal" />
+
+    <ImageView
+        android:layout_width="200dp"
+        android:layout_height="wrap_content"
+        android:id="@+id/imageView3"
+        android:src="@drawable/dhbw_logo"
+        android:adjustViewBounds="true"
+        android:layout_margin="8dp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Loading..."
+        android:id="@+id/textView"
+        android:layout_above="@+id/splashProgress"
+        android:layout_centerHorizontal="true" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_vorlesungsplan.xml b/app/src/main/res/layout/fragment_vorlesungsplan.xml
new file mode 100644 (file)
index 0000000..69d2dce
--- /dev/null
@@ -0,0 +1,13 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    tools:context="de.dhbwloe.campusapp.fragments.Vorlesungsplan">
+
+    <!-- TODO: Update blank fragment layout -->
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:text="{Vorlesungsplan}" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/fragment_web_browser.xml b/app/src/main/res/layout/fragment_web_browser.xml
new file mode 100644 (file)
index 0000000..d5bb56a
--- /dev/null
@@ -0,0 +1,12 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="de.dhbwloe.campusapp.fragments.WebBrowser">
+
+    <WebView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/browserWebView" />
+
+</FrameLayout>
diff --git a/app/src/main/res/layout/fragment_wifi_settings.xml b/app/src/main/res/layout/fragment_wifi_settings.xml
new file mode 100644 (file)
index 0000000..98f09d3
--- /dev/null
@@ -0,0 +1,167 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="de.dhbwloe.campusapp.fragments.WifiSettings"
+    android:padding="8dp">
+
+    <!-- TODO: Update blank fragment layout -->
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/wifisettings_about_wifi" />
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="8dp">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/wifisettings_ssid_caption"
+            android:id="@+id/textView3" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/wifisettings_security_caption"
+            android:id="@+id/textView4"
+            android:layout_below="@+id/textView3"
+            android:layout_alignLeft="@+id/textView3"
+            android:layout_alignStart="@+id/textView3" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/wifisettings_eap_caption"
+            android:id="@+id/textView6"
+            android:layout_below="@+id/textView4"
+            android:layout_alignLeft="@+id/textView3"
+            android:layout_alignStart="@+id/textView4" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/wifisettings_phase2_caption"
+            android:id="@+id/textView7"
+            android:layout_below="@+id/textView6"
+            android:layout_alignLeft="@+id/textView3"
+            android:layout_alignStart="@+id/textView6" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/wifisettings_cacert_caption"
+            android:id="@+id/textView8"
+            android:layout_below="@+id/textView7"
+            android:layout_alignLeft="@+id/textView3"
+            android:layout_alignStart="@+id/textView7" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="{ssid}"
+            android:id="@+id/ssidInfo"
+            android:paddingLeft="8dp"
+            android:layout_toRightOf="@+id/textView7"
+            android:layout_toEndOf="@+id/textView7" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="{security}"
+            android:id="@+id/securityInfo"
+            android:paddingLeft="8dp"
+            android:layout_below="@+id/textView3"
+            android:layout_toRightOf="@+id/textView7"
+            android:layout_toEndOf="@+id/textView7" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="{eap}"
+            android:id="@+id/eapInfo"
+            android:paddingLeft="8dp"
+            android:layout_below="@+id/textView4"
+            android:layout_toRightOf="@+id/textView7"
+            android:layout_toEndOf="@+id/textView7" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="{phase2}"
+            android:id="@+id/phase2Info"
+            android:paddingLeft="8dp"
+            android:layout_below="@+id/textView6"
+            android:layout_toRightOf="@+id/textView7"
+            android:layout_toEndOf="@+id/textView7" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="{cacert}"
+            android:id="@+id/cacertInfo"
+            android:paddingLeft="8dp"
+            android:layout_below="@+id/textView7"
+            android:layout_toRightOf="@+id/textView7"
+            android:layout_toEndOf="@+id/textView7" />
+    </RelativeLayout>
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="8dp">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/wifisettings_username_caption"
+            android:id="@+id/textView5"
+            android:layout_alignBottom="@+id/wifiUsername"
+            android:layout_marginBottom="8dp" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/wifisettings_password_caption"
+            android:id="@+id/textView9"
+            android:layout_alignBottom="@+id/wifiPassword"
+            android:layout_marginBottom="8dp" />
+
+        <EditText
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/wifiUsername"
+            android:layout_toRightOf="@+id/textView5"
+            android:layout_marginLeft="8dp"
+            android:layout_alignRight="@+id/wifiPassword"
+            android:layout_alignEnd="@+id/wifiPassword" />
+
+        <EditText
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:inputType="textPassword"
+            android:ems="10"
+            android:id="@+id/wifiPassword"
+            android:layout_centerHorizontal="true"
+            android:layout_below="@+id/wifiUsername"
+            android:layout_toRightOf="@+id/textView5"
+            android:layout_marginLeft="8dp" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal">
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/wifisettings_connect_button"
+            android:id="@+id/wifiConnectBtn"
+            android:layout_alignParentTop="true"
+            android:layout_centerHorizontal="true" />
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/nav_header_campus_app.xml b/app/src/main/res/layout/nav_header_campus_app.xml
new file mode 100644 (file)
index 0000000..6de2f2c
--- /dev/null
@@ -0,0 +1,56 @@
+<?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="@dimen/nav_header_height"
+    android:background="@drawable/dhbw_campus"
+    android:gravity="bottom"
+    android:orientation="vertical"
+    android:theme="@style/ThemeOverlay.AppCompat.Dark"
+    android:weightSum="1">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <ImageView
+            android:id="@+id/imageView"
+            android:layout_width="140dp"
+            android:layout_height="wrap_content"
+            android:paddingTop="@dimen/nav_header_vertical_spacing"
+            android:src="@drawable/dhbw_logo"
+            android:adjustViewBounds="true"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:layout_marginTop="-8dp"
+            android:layout_marginLeft="8dp" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:text="Lörrach"
+            android:id="@+id/textView2"
+            android:textColor="#5d6971"
+            android:layout_alignBottom="@+id/imageView"
+            android:layout_toRightOf="@+id/imageView"
+            android:layout_toEndOf="@+id/imageView"
+            android:layout_marginBottom="18dp"
+            android:layout_marginLeft="8dp"
+            android:textSize="28sp" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingTop="@dimen/nav_header_vertical_spacing"
+            android:text="Campus App"
+            android:textAppearance="@style/TextAppearance.AppCompat.Body1"
+            android:textSize="22sp"
+            android:textColor="#ffffff"
+            android:layout_alignLeft="@+id/textView2"
+            android:layout_alignStart="@+id/textView2"
+            android:layout_alignTop="@+id/textView2"
+            android:layout_marginTop="12dp" />
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/app/src/main/res/menu/activity_campus_app_drawer.xml b/app/src/main/res/menu/activity_campus_app_drawer.xml
new file mode 100644 (file)
index 0000000..37e5d1a
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <group android:checkableBehavior="single" android:title="Campus App">
+        <item
+            android:id="@+id/nav_dashboard"
+            android:icon="@drawable/ic_menu_camera"
+            android:title="Dashboard" />
+        <item
+            android:id="@+id/nav_vorlesungsplan"
+            android:icon="@drawable/ic_menu_gallery"
+            android:title="Vorlesungsplan" />
+        <item
+            android:id="@+id/nav_mensa"
+            android:icon="@drawable/ic_menu_slideshow"
+            android:title="Mensa" />
+        <item
+            android:id="@+id/nav_news"
+            android:icon="@drawable/ic_menu_send"
+            android:title="News" />
+        <item
+            android:id="@+id/nav_settings"
+            android:icon="@drawable/ic_menu_share"
+            android:title="Settings" />
+        <item
+            android:id="@+id/nav_impressum"
+            android:icon="@drawable/ic_menu_send"
+            android:title="Impressum" />
+    </group>
+
+</menu>
diff --git a/app/src/main/res/menu/campus_app.xml b/app/src/main/res/menu/campus_app.xml
new file mode 100644 (file)
index 0000000..a2411e3
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/action_settings"
+        android:orderInCategory="100"
+        android:title="@string/action_settings"
+        app:showAsAction="never" />
+</menu>
diff --git a/app/src/main/res/mipmap-hdpi/ic_close_white.png b/app/src/main/res/mipmap-hdpi/ic_close_white.png
new file mode 100644 (file)
index 0000000..79f6bc9
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_close_white.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..cde69bc
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_search.png b/app/src/main/res/mipmap-hdpi/ic_search.png
new file mode 100644 (file)
index 0000000..ada82b8
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_search.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_close_white.png b/app/src/main/res/mipmap-mdpi/ic_close_white.png
new file mode 100644 (file)
index 0000000..edf5248
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_close_white.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..c133a0c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_search.png b/app/src/main/res/mipmap-mdpi/ic_search.png
new file mode 100644 (file)
index 0000000..0029cc4
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_search.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_close_white.png b/app/src/main/res/mipmap-xhdpi/ic_close_white.png
new file mode 100644 (file)
index 0000000..fde1038
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_close_white.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..bfa42f0
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_search.png b/app/src/main/res/mipmap-xhdpi/ic_search.png
new file mode 100644 (file)
index 0000000..02ea55d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_search.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_close_white.png b/app/src/main/res/mipmap-xxhdpi/ic_close_white.png
new file mode 100644 (file)
index 0000000..9e79276
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_close_white.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..324e72c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_search.png b/app/src/main/res/mipmap-xxhdpi/ic_search.png
new file mode 100644 (file)
index 0000000..2154a7d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_search.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_close_white.png b/app/src/main/res/mipmap-xxxhdpi/ic_close_white.png
new file mode 100644 (file)
index 0000000..e6b91b9
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_close_white.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..aee44e1
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_search.png b/app/src/main/res/mipmap-xxxhdpi/ic_search.png
new file mode 100644 (file)
index 0000000..a10f6d0
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_search.png differ
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
new file mode 100644 (file)
index 0000000..251fb9f
--- /dev/null
@@ -0,0 +1,9 @@
+<resources>>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+    </style>
+</resources>
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644 (file)
index 0000000..63fc816
--- /dev/null
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644 (file)
index 0000000..3ab3e9c
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/app/src/main/res/values/customids.xml b/app/src/main/res/values/customids.xml
new file mode 100644 (file)
index 0000000..2dfa89e
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <item name="popupfragment" type="id">popupfragment</item>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644 (file)
index 0000000..c2effc5
--- /dev/null
@@ -0,0 +1,9 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="nav_header_vertical_spacing">16dp</dimen>
+    <dimen name="nav_header_height">160dp</dimen>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="fab_margin">16dp</dimen>
+</resources>
diff --git a/app/src/main/res/values/drawables.xml b/app/src/main/res/values/drawables.xml
new file mode 100644 (file)
index 0000000..52c6a6c
--- /dev/null
@@ -0,0 +1,8 @@
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <item name="ic_menu_camera" type="drawable">@android:drawable/ic_menu_camera</item>
+    <item name="ic_menu_gallery" type="drawable">@android:drawable/ic_menu_gallery</item>
+    <item name="ic_menu_slideshow" type="drawable">@android:drawable/ic_menu_slideshow</item>
+    <item name="ic_menu_manage" type="drawable">@android:drawable/ic_menu_manage</item>
+    <item name="ic_menu_share" type="drawable">@android:drawable/ic_menu_share</item>
+    <item name="ic_menu_send" type="drawable">@android:drawable/ic_menu_send</item>
+</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644 (file)
index 0000000..0bcc8f1
--- /dev/null
@@ -0,0 +1,82 @@
+<resources>
+    <string name="app_name">DHBW Campus App</string>
+
+    <string name="navigation_drawer_open">Open navigation drawer</string>
+    <string name="navigation_drawer_close">Close navigation drawer</string>
+
+    <string name="action_settings">Settings</string>
+
+    <string name="wifisettings_about_wifi">Die DHBW Lörrach bietet den Studenten verschiedene Möglichkeiten Geräte mit dem W-LAN zu verbinden. Mithilfe dieser App kannst du dein Gerät dazu konfigurieren sich unter berücksichtigung sicherheitsrelevanter Überprüfungen mit dem dhbw-secure Netzwerk zu verbinden.</string>
+    <string name="wifisettings_ssid_caption">SSID:</string>
+    <string name="wifisettings_security_caption">Security:</string>
+    <string name="wifisettings_eap_caption">EAP-Methode:</string>
+    <string name="wifisettings_phase2_caption">Phase 2-Authentifizierung:</string>
+    <string name="wifisettings_cacert_caption">CA-Zertifikat:</string>
+    <string name="wifisettings_username_caption">Username:</string>
+    <string name="wifisettings_password_caption">Passwort:</string>
+
+    <string name="wifisettings_connect_button">Jetzt Verbinden</string>
+    <string name="title_activity_settings">Settings</string>
+
+    <!-- Strings related to Settings -->
+
+    <!-- Example General settings -->
+    <string name="pref_header_general">General</string>
+
+    <string name="pref_title_social_recommendations">Enable social recommendations</string>
+    <string name="pref_description_social_recommendations">Recommendations for people to contact
+        based on your message history
+    </string>
+
+    <string name="pref_title_display_name">Display name</string>
+    <string name="pref_default_display_name">John Smith</string>
+
+    <string name="pref_title_add_friends_to_messages">Add friends to messages</string>
+    <string-array name="pref_example_list_titles">
+        <item>Always</item>
+        <item>When possible</item>
+        <item>Never</item>
+    </string-array>
+    <string-array name="pref_example_list_values">
+        <item>1</item>
+        <item>0</item>
+        <item>-1</item>
+    </string-array>
+
+    <!-- Example settings for Data & Sync -->
+    <string name="pref_header_data_sync">Data &amp; sync</string>
+
+    <string name="pref_title_sync_frequency">Sync frequency</string>
+    <string-array name="pref_sync_frequency_titles">
+        <item>15 minutes</item>
+        <item>30 minutes</item>
+        <item>1 hour</item>
+        <item>3 hours</item>
+        <item>6 hours</item>
+        <item>Never</item>
+    </string-array>
+    <string-array name="pref_sync_frequency_values">
+        <item>15</item>
+        <item>30</item>
+        <item>60</item>
+        <item>180</item>
+        <item>360</item>
+        <item>-1</item>
+    </string-array>
+
+    <string name="pref_title_system_sync_settings">System sync settings</string>
+
+    <!-- Example settings for Notifications -->
+    <string name="pref_header_notifications">Notifications</string>
+
+    <string name="pref_title_new_message_notifications">New message notifications</string>
+
+    <string name="pref_title_ringtone">Ringtone</string>
+    <string name="pref_ringtone_silent">Silent</string>
+
+    <string name="pref_title_vibrate">Vibrate</string>
+
+    <!-- TODO: Remove or change this placeholder text -->
+    <string name="hello_blank_fragment">Hello blank fragment</string>
+    <string name="mensacard_guthaben">Dein Guthaben:</string>
+</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644 (file)
index 0000000..545b9c6
--- /dev/null
@@ -0,0 +1,20 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+</resources>
diff --git a/app/src/main/res/xml/pref_data_sync.xml b/app/src/main/res/xml/pref_data_sync.xml
new file mode 100644 (file)
index 0000000..6bd9192
--- /dev/null
@@ -0,0 +1,21 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
+         dismiss it. -->
+    <!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
+    <ListPreference
+        android:defaultValue="180"
+        android:entries="@array/pref_sync_frequency_titles"
+        android:entryValues="@array/pref_sync_frequency_values"
+        android:key="sync_frequency"
+        android:negativeButtonText="@null"
+        android:positiveButtonText="@null"
+        android:title="@string/pref_title_sync_frequency" />
+
+    <!-- This preference simply launches an intent when selected. Use this UI sparingly, per
+         design guidelines. -->
+    <Preference android:title="@string/pref_title_system_sync_settings">
+        <intent android:action="android.settings.SYNC_SETTINGS" />
+    </Preference>
+
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml
new file mode 100644 (file)
index 0000000..36569d6
--- /dev/null
@@ -0,0 +1,33 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreference
+        android:defaultValue="true"
+        android:key="example_switch"
+        android:summary="@string/pref_description_social_recommendations"
+        android:title="@string/pref_title_social_recommendations" />
+
+    <!-- NOTE: EditTextPreference accepts EditText attributes. -->
+    <!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
+    <EditTextPreference
+        android:capitalize="words"
+        android:defaultValue="@string/pref_default_display_name"
+        android:inputType="textCapWords"
+        android:key="example_text"
+        android:maxLines="1"
+        android:selectAllOnFocus="true"
+        android:singleLine="true"
+        android:title="@string/pref_title_display_name" />
+
+    <!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
+         dismiss it. -->
+    <!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
+    <ListPreference
+        android:defaultValue="-1"
+        android:entries="@array/pref_example_list_titles"
+        android:entryValues="@array/pref_example_list_values"
+        android:key="example_list"
+        android:negativeButtonText="@null"
+        android:positiveButtonText="@null"
+        android:title="@string/pref_title_add_friends_to_messages" />
+
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/pref_headers.xml b/app/src/main/res/xml/pref_headers.xml
new file mode 100644 (file)
index 0000000..367d096
--- /dev/null
@@ -0,0 +1,20 @@
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- These settings headers are only used on tablets. -->
+
+    <header
+        android:fragment="de.dhbwloe.campusapp.SettingsActivity$GeneralPreferenceFragment"
+        android:icon="@drawable/ic_info_black_24dp"
+        android:title="@string/pref_header_general" />
+
+    <header
+        android:fragment="de.dhbwloe.campusapp.SettingsActivity$NotificationPreferenceFragment"
+        android:icon="@drawable/ic_notifications_black_24dp"
+        android:title="@string/pref_header_notifications" />
+
+    <header
+        android:fragment="de.dhbwloe.campusapp.SettingsActivity$DataSyncPreferenceFragment"
+        android:icon="@drawable/ic_sync_black_24dp"
+        android:title="@string/pref_header_data_sync" />
+
+</preference-headers>
diff --git a/app/src/main/res/xml/pref_notification.xml b/app/src/main/res/xml/pref_notification.xml
new file mode 100644 (file)
index 0000000..e5a319e
--- /dev/null
@@ -0,0 +1,27 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- A 'parent' preference, which enables/disables child preferences (below)
+         when checked/unchecked. -->
+    <SwitchPreference
+        android:defaultValue="true"
+        android:key="notifications_new_message"
+        android:title="@string/pref_title_new_message_notifications" />
+
+    <!-- Allows the user to choose a ringtone in the 'notification' category. -->
+    <!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
+    <!-- NOTE: RingtonePreference's summary should be set to its value by the activity code. -->
+    <RingtonePreference
+        android:defaultValue="content://settings/system/notification_sound"
+        android:dependency="notifications_new_message"
+        android:key="notifications_new_message_ringtone"
+        android:ringtoneType="notification"
+        android:title="@string/pref_title_ringtone" />
+
+    <!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
+    <SwitchPreference
+        android:defaultValue="true"
+        android:dependency="notifications_new_message"
+        android:key="notifications_new_message_vibrate"
+        android:title="@string/pref_title_vibrate" />
+
+</PreferenceScreen>
diff --git a/app/src/test/java/de/dhbwloe/campusapp/ExampleUnitTest.java b/app/src/test/java/de/dhbwloe/campusapp/ExampleUnitTest.java
new file mode 100644 (file)
index 0000000..e45d813
--- /dev/null
@@ -0,0 +1,15 @@
+package de.dhbwloe.campusapp;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() throws Exception {
+        assertEquals(4, 2 + 2);
+    }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644 (file)
index 0000000..e0b366a
--- /dev/null
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.5.0'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644 (file)
index 0000000..1d3591c
--- /dev/null
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644 (file)
index 0000000..05ef575
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644 (file)
index 0000000..f23df6e
--- /dev/null
@@ -0,0 +1,6 @@
+#Wed Oct 21 11:34:03 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644 (file)
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644 (file)
index 0000000..8a0b282
--- /dev/null
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644 (file)
index 0000000..e7b4def
--- /dev/null
@@ -0,0 +1 @@
+include ':app'