New MobiAds Android implementation.
authorgumartinm <gustavo@gumartinm.name>
Wed, 20 Jun 2012 06:36:51 +0000 (08:36 +0200)
committergumartinm <gustavo@gumartinm.name>
Wed, 20 Jun 2012 06:36:51 +0000 (08:36 +0200)
Because the tabs are really complicated and they (IMHO)
do not suit my user's experience.

33 files changed:
Android/MobiAdsReloaded/AndroidManifest.xml [new file with mode: 0644]
Android/MobiAdsReloaded/gen/de/android/mobiads/BuildConfig.java [new file with mode: 0644]
Android/MobiAdsReloaded/gen/de/android/mobiads/R.java [new file with mode: 0644]
Android/MobiAdsReloaded/proguard-project.txt [new file with mode: 0644]
Android/MobiAdsReloaded/project.properties [new file with mode: 0644]
Android/MobiAdsReloaded/res/drawable-hdpi/ic_launcher.png [new file with mode: 0644]
Android/MobiAdsReloaded/res/drawable-ldpi/ic_launcher.png [new file with mode: 0644]
Android/MobiAdsReloaded/res/drawable-mdpi/ic_launcher.png [new file with mode: 0644]
Android/MobiAdsReloaded/res/drawable-xhdpi/ic_launcher.png [new file with mode: 0644]
Android/MobiAdsReloaded/res/drawable/wheelnotification.xml [new file with mode: 0644]
Android/MobiAdsReloaded/res/layout/ads_entry_list_item.xml [new file with mode: 0644]
Android/MobiAdsReloaded/res/layout/latest_ads.xml [new file with mode: 0644]
Android/MobiAdsReloaded/res/layout/login.xml [new file with mode: 0644]
Android/MobiAdsReloaded/res/layout/main.xml [new file with mode: 0644]
Android/MobiAdsReloaded/res/menu/menuads.xml [new file with mode: 0644]
Android/MobiAdsReloaded/res/menu/selectedmenuads.xml [new file with mode: 0644]
Android/MobiAdsReloaded/res/values/strings.xml [new file with mode: 0644]
Android/MobiAdsReloaded/res/xml/preferences.xml [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/Cookie.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsLoginActivity.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsPreferences.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsService.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsTabsActivity.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/batch/MobiAdsBatch.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/list/AdsEntry.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/list/AdsEntryAdapter.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsLatest.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsLatestList.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsList.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsNewAdsActivity.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/provider/Indexer.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/provider/IndexerOpenHelper.java [new file with mode: 0644]
Android/MobiAdsReloaded/src/de/android/mobiads/provider/IndexerProvider.java [new file with mode: 0644]

diff --git a/Android/MobiAdsReloaded/AndroidManifest.xml b/Android/MobiAdsReloaded/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..8140b7f
--- /dev/null
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.android.mobiads"
+    android:versionCode="1"
+    android:versionName="1.0"
+    android:installLocation="internalOnly" >
+    
+    <supports-screens android:smallScreens="true"
+                      android:normalScreens="true"
+                      android:largeScreens="false"
+                      android:xlargeScreens="false"
+                      android:requiresSmallestWidthDp="400" 
+                      android:largestWidthLimitDp="800"/>
+    
+
+    <uses-sdk android:minSdkVersion="15" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
+
+    <application
+        android:icon="@drawable/ic_launcher" android:label="@string/app_name" 
+        android:description="@string/app_description" android:hardwareAccelerated="false" 
+        android:largeHeap="false" android:testOnly="false">
+        
+        <activity
+            android:label="@string/app_name"
+            android:name=".list.MobiAdsList"
+            android:theme="@android:style/Theme.Holo"
+            android:screenOrientation="portrait"
+            android:configChanges="touchscreen|keyboard"
+            android:launchMode="standard"
+            android:uiOptions="splitActionBarWhenNarrow" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+            android:label="@string/app_name"
+            android:name=".MobiAdsLoginActivity" 
+            android:screenOrientation="portrait" 
+            android:configChanges="touchscreen|keyboard"
+            android:theme="@android:style/Theme.Black" 
+            android:launchMode="standard"
+            android:description="@string/app_description">
+            <intent-filter >
+                <action android:name="android.intent.action.MOBIADSLOGIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+            android:label="@string/app_name"
+            android:name=".list.MobiAdsLatestList"
+            android:theme="@android:style/Theme.Dialog" 
+            android:screenOrientation="portrait"
+            android:configChanges="touchscreen|keyboard"
+            android:noHistory="true"
+            android:taskAffinity="de.android.mobiads.list">
+            <intent-filter>
+                <action android:name="android.intent.action.MOBIADSNEWADS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        
+        <service
+            android:name=".MobiAdsService"
+            android:process=":mobiadsservice" >
+        </service>
+        
+        <provider 
+            android:authorities="de.android.mobiads.provider"
+            android:enabled="true"
+            android:exported="false"
+            android:grantUriPermissions="false"
+            android:icon="@drawable/ic_launcher"
+            android:initOrder="1"
+            android:label="@string/app_name"
+            android:multiprocess="false"
+            android:name=".provider.IndexerProvider"
+            android:permission="android.permission.MOBIADS"
+            android:readPermission="android.permission.READ_MOBIADS"
+            android:syncable="false"
+            android:writePermission="android.permission.WRITE_MOBIADS">
+            <grant-uri-permission android:pathPattern=".*" />
+        </provider>
+        
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/gen/de/android/mobiads/BuildConfig.java b/Android/MobiAdsReloaded/gen/de/android/mobiads/BuildConfig.java
new file mode 100644 (file)
index 0000000..5ab5a24
--- /dev/null
@@ -0,0 +1,6 @@
+/** Automatically generated file. DO NOT MODIFY */
+package de.android.mobiads;
+
+public final class BuildConfig {
+    public final static boolean DEBUG = true;
+}
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/gen/de/android/mobiads/R.java b/Android/MobiAdsReloaded/gen/de/android/mobiads/R.java
new file mode 100644 (file)
index 0000000..7db5fe9
--- /dev/null
@@ -0,0 +1,84 @@
+/* AUTO-GENERATED FILE.  DO NOT MODIFY.
+ *
+ * This class was automatically generated by the
+ * aapt tool from the resource data it found.  It
+ * should not be modified by hand.
+ */
+
+package de.android.mobiads;
+
+public final class R {
+    public static final class attr {
+    }
+    public static final class drawable {
+        public static final int ic_launcher=0x7f020000;
+        public static final int wheelnotification=0x7f020001;
+    }
+    public static final class id {
+        public static final int ads_entry_icon=0x7f070000;
+        public static final int ads_entry_text=0x7f070002;
+        public static final int ads_entry_title=0x7f070001;
+        public static final int frameLayout1=0x7f070004;
+        public static final int frameLayout2=0x7f070007;
+        public static final int list_frag=0x7f070003;
+        public static final int login_button=0x7f070008;
+        public static final int menu_gus=0x7f07000c;
+        public static final int menu_gusa=0x7f07000d;
+        public static final int menu_gusas=0x7f07000e;
+        public static final int menu_gusass=0x7f070010;
+        public static final int menu_gusasss=0x7f07000f;
+        public static final int menu_prueba=0x7f07000b;
+        public static final int menu_save=0x7f07000a;
+        public static final int menuadsremove=0x7f070009;
+        public static final int password=0x7f070006;
+        public static final int selectedmenuads=0x7f070011;
+        public static final int username=0x7f070005;
+    }
+    public static final class layout {
+        public static final int ads_entry_list_item=0x7f030000;
+        public static final int latest_ads=0x7f030001;
+        public static final int login=0x7f030002;
+        public static final int main=0x7f030003;
+    }
+    public static final class menu {
+        public static final int menuads=0x7f060000;
+        public static final int selectedmenuads=0x7f060001;
+    }
+    public static final class string {
+        public static final int alert_dialog_logged=0x7f050004;
+        public static final int app_description=0x7f050003;
+        public static final int app_name=0x7f050002;
+        public static final int button_cancel=0x7f050010;
+        public static final int button_localads=0x7f050011;
+        public static final int button_login=0x7f050009;
+        public static final int button_messagebind=0x7f05000a;
+        public static final int button_messagelistlocalads=0x7f05000e;
+        public static final int button_messagestartservice=0x7f05000d;
+        public static final int button_messagestopservice=0x7f05000c;
+        public static final int button_messageunbind=0x7f05000b;
+        public static final int button_ok=0x7f05000f;
+        public static final int desc=0x7f05001d;
+        public static final int encoded_web_service=0x7f05001c;
+        public static final int error_dialog_connection_error=0x7f050005;
+        public static final int error_dialog_no_local_ads=0x7f05001f;
+        public static final int error_dialog_userpwd_error=0x7f050006;
+        public static final int header_bar=0x7f050000;
+        public static final int menuads_remove=0x7f05001e;
+        public static final int new_ads=0x7f050001;
+        public static final int password=0x7f050008;
+        public static final int remote_service_content_empty_notification=0x7f050015;
+        public static final int remote_service_content_notification=0x7f050014;
+        public static final int remote_service_new_ads=0x7f050012;
+        public static final int remote_service_received_ad_notification=0x7f050013;
+        public static final int remote_service_started_notification=0x7f050016;
+        public static final int remote_service_stopped_notification=0x7f050017;
+        public static final int remote_service_title_notification=0x7f050018;
+        public static final int url_login_web_service=0x7f050019;
+        public static final int url_web=0x7f05001a;
+        public static final int user_agent_web_service=0x7f05001b;
+        public static final int username=0x7f050007;
+    }
+    public static final class xml {
+        public static final int preferences=0x7f040000;
+    }
+}
diff --git a/Android/MobiAdsReloaded/proguard-project.txt b/Android/MobiAdsReloaded/proguard-project.txt
new file mode 100644 (file)
index 0000000..f2fe155
--- /dev/null
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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/Android/MobiAdsReloaded/project.properties b/Android/MobiAdsReloaded/project.properties
new file mode 100644 (file)
index 0000000..0840b4a
--- /dev/null
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-15
diff --git a/Android/MobiAdsReloaded/res/drawable-hdpi/ic_launcher.png b/Android/MobiAdsReloaded/res/drawable-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..96a442e
Binary files /dev/null and b/Android/MobiAdsReloaded/res/drawable-hdpi/ic_launcher.png differ
diff --git a/Android/MobiAdsReloaded/res/drawable-ldpi/ic_launcher.png b/Android/MobiAdsReloaded/res/drawable-ldpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..9923872
Binary files /dev/null and b/Android/MobiAdsReloaded/res/drawable-ldpi/ic_launcher.png differ
diff --git a/Android/MobiAdsReloaded/res/drawable-mdpi/ic_launcher.png b/Android/MobiAdsReloaded/res/drawable-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..359047d
Binary files /dev/null and b/Android/MobiAdsReloaded/res/drawable-mdpi/ic_launcher.png differ
diff --git a/Android/MobiAdsReloaded/res/drawable-xhdpi/ic_launcher.png b/Android/MobiAdsReloaded/res/drawable-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..71c6d76
Binary files /dev/null and b/Android/MobiAdsReloaded/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/Android/MobiAdsReloaded/res/drawable/wheelnotification.xml b/Android/MobiAdsReloaded/res/drawable/wheelnotification.xml
new file mode 100644 (file)
index 0000000..66da9db
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<level-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:maxLevel="0" android:drawable="@drawable/ic_launcher" />
+    <item android:maxLevel="1" android:drawable="@drawable/ic_launcher" />
+</level-list>
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/res/layout/ads_entry_list_item.xml b/Android/MobiAdsReloaded/res/layout/ads_entry_list_item.xml
new file mode 100644 (file)
index 0000000..d5eea08
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Layout for individual ads entries in a list -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+    
+    <!-- Icon shown next to the title/text -->
+    <ImageView
+        android:id="@+id/ads_entry_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:padding="3dp"
+        android:orientation="vertical"
+        android:background="?android:attr/activatedBackgroundIndicator"
+        android:contentDescription="@string/desc"/>
+    
+    <!-- Title of the ads entry -->
+    <TextView
+        android:id="@+id/ads_entry_title"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/ads_entry_icon"
+        android:layout_alignTop="@id/ads_entry_icon"
+           android:layout_margin="5dp"
+        android:textSize="14sp"
+        android:textStyle="bold" />
+    
+    <!-- Text of our entry -->
+    <TextView
+        android:id="@+id/ads_entry_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignLeft="@id/ads_entry_title"
+        android:layout_below="@id/ads_entry_title" 
+        android:textSize="12sp" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/res/layout/latest_ads.xml b/Android/MobiAdsReloaded/res/layout/latest_ads.xml
new file mode 100644 (file)
index 0000000..6286be1
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <fragment class="de.android.mobiads.list.MobiAdsLatest$MobiAdsListFragment"
+              android:id="@+id/list_frag"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/res/layout/login.xml b/Android/MobiAdsReloaded/res/layout/login.xml
new file mode 100644 (file)
index 0000000..5ada372
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:gravity="right"
+    android:layout_width="match_parent" 
+    android:layout_height="match_parent" 
+    android:orientation="vertical" 
+    android:weightSum="1">
+    
+    <FrameLayout 
+        android:id="@+id/frameLayout1" 
+        android:layout_width="match_parent" 
+        android:layout_height="70dp">
+    </FrameLayout>
+        
+    <EditText
+        android:id="@+id/username"
+        android:singleLine="true"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" 
+        android:hint="@string/username"/>
+    
+    <EditText
+        android:id="@+id/password"
+        android:inputType="textPassword"
+        android:singleLine="true"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/password"/>
+    <FrameLayout 
+        android:layout_width="match_parent" 
+        android:id="@+id/frameLayout2" 
+        android:layout_height="30dp">
+    </FrameLayout>
+    <Button
+        android:id="@+id/login_button"
+        android:onClick="onClickLogin"
+        android:layout_height="wrap_content" 
+        android:text="@string/button_login" 
+        android:layout_width="match_parent" 
+        android:layout_gravity="center"/>
+    
+</LinearLayout>
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/res/layout/main.xml b/Android/MobiAdsReloaded/res/layout/main.xml
new file mode 100644 (file)
index 0000000..e74e9b3
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+    <TabHost xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@android:id/tabhost"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:padding="5dp">
+            <TabWidget
+                android:id="@android:id/tabs"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+            <FrameLayout
+                android:id="@android:id/tabcontent"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:padding="5dp" />
+        </LinearLayout>
+    </TabHost>
+</LinearLayout>
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/res/menu/menuads.xml b/Android/MobiAdsReloaded/res/menu/menuads.xml
new file mode 100644 (file)
index 0000000..979dae7
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+      <item android:id="@+id/menuadsremove"
+          android:icon="@android:drawable/ic_menu_upload"
+          android:title="@string/menuads_remove"
+          android:titleCondensed="@string/menuads_remove"
+          android:showAsAction="ifRoom|withText"
+          android:alphabeticShortcut="string"
+          android:numericShortcut="string"
+          android:checkable="false"
+          android:visible="true"
+          android:enabled="true" />
+      <item android:id="@+id/menu_save"
+          android:icon="@android:drawable/ic_menu_save"
+          android:title="@string/menuads_remove"
+          android:visible="true"
+          android:showAsAction="ifRoom|withText" />
+      <item android:id="@+id/menu_prueba"
+          android:icon="@android:drawable/ic_menu_upload"
+          android:title="@string/menuads_remove"
+          android:visible="true"
+          android:showAsAction="ifRoom|withText" />
+      <item android:id="@+id/menu_gus"
+          android:icon="@android:drawable/ic_menu_upload"
+          android:title="@string/menuads_remove"
+          android:visible="true"
+          android:showAsAction="ifRoom|withText" />
+      
+      <item android:id="@+id/menu_gusa"
+          android:icon="@android:drawable/ic_menu_upload"
+          android:title="@string/menuads_remove"
+          android:visible="true"
+          android:showAsAction="ifRoom|withText" />
+      <item android:id="@+id/menu_gusas"
+          android:icon="@android:drawable/ic_menu_upload"
+          android:title="@string/menuads_remove"
+          android:visible="true"
+          android:showAsAction="ifRoom|withText" />
+      
+      <item android:id="@+id/menu_gusasss"
+          android:icon="@android:drawable/ic_menu_upload"
+          android:title="@string/menuads_remove"
+          android:visible="true"
+          android:showAsAction="ifRoom|withText" />
+      
+      
+      <item android:id="@+id/menu_gusass"
+          android:icon="@android:drawable/ic_menu_upload"
+          android:title="@string/menuads_remove"
+          android:visible="true"
+          android:showAsAction="ifRoom|withText" /> 
+     
+      <!-- I want overflow menu button if there is no room: android:showAsAction="ifRoom|withText" -->
+</menu>
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/res/menu/selectedmenuads.xml b/Android/MobiAdsReloaded/res/menu/selectedmenuads.xml
new file mode 100644 (file)
index 0000000..e9c31cf
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+      <item android:id="@+id/selectedmenuads"
+          android:icon="@android:drawable/ic_menu_upload"
+          android:title="@string/menuads_remove"
+          android:titleCondensed="@string/menuads_remove"
+          android:showAsAction="never"
+          android:alphabeticShortcut="string"
+          android:numericShortcut="string"
+          android:checkable="false"
+          android:visible="true"
+          android:enabled="true" />
+      
+      <!-- I want overflow menu button when showing the removing action: android:showAsAction="never" -->
+</menu>
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/res/values/strings.xml b/Android/MobiAdsReloaded/res/values/strings.xml
new file mode 100644 (file)
index 0000000..c1dd817
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="header_bar">MobiAds</string>
+    <string name="new_ads">Latest received Ads</string>
+    <string name="app_name">MobiAds</string>
+    <string name="app_description">Receive notifications about offers and discounts.</string>
+    <string name="alert_dialog_logged">You are already logged in</string>
+    <string name="error_dialog_connection_error">Connection error with MobiAds server.</string>
+    <string name="error_dialog_userpwd_error">The username or password you entered is incorrect.</string>
+    <string name="username">Username</string>
+    <string name="password">Password</string>
+    <string name="button_login">Log In</string>
+    <string name="button_messagebind">Bind Service</string>
+    <string name="button_messageunbind">Unbind Service</string>
+    <string name="button_messagestopservice">Stop Application</string>
+    <string name="button_messagestartservice">Start Application</string>
+    <string name="button_messagelistlocalads">List Your Ads</string>
+    <string name="button_ok">OK</string>
+    <string name="button_cancel">Cancel</string>
+    <string name="button_localads">Local Ads</string>
+    <string name="remote_service_new_ads"> new Ads downloaded for you.</string>
+    <string name="remote_service_received_ad_notification">Received Ad</string>
+    <string name="remote_service_content_notification">New Ads waiting for you</string>
+    <string name="remote_service_content_empty_notification">No new Ads waiting for you</string>
+    <string name="remote_service_started_notification">MobiAds Service Started</string>
+    <string name="remote_service_stopped_notification">MobiAds Service Stopped</string>
+    <string name="remote_service_title_notification">MobiAds Service</string>
+    <string name="url_login_web_service">http://users.mobiads.gumartinm.name/userfront.php/api/login/auth.json</string>
+    <string name="url_web">users.mobiads.gumartinm.name</string>
+    <string name="user_agent_web_service">MobiAds/1.0</string>
+    <string name="encoded_web_service">UTF-8</string>
+    <string name="desc">description</string>
+    <string name="menuads_remove">Remove</string>
+    <string name="error_dialog_no_local_ads">You have no downloaded ads.</string>
+</resources>
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/res/xml/preferences.xml b/Android/MobiAdsReloaded/res/xml/preferences.xml
new file mode 100644 (file)
index 0000000..48c7aba
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:key="preferencescreen">
+        
+    <CheckBoxPreference
+            android:key="service_started"
+            android:title="Service started" 
+            android:enabled="true" 
+            android:selectable="true"/>
+</PreferenceScreen> 
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/Cookie.java b/Android/MobiAdsReloaded/src/de/android/mobiads/Cookie.java
new file mode 100644 (file)
index 0000000..e9a9f60
--- /dev/null
@@ -0,0 +1,13 @@
+package de.android.mobiads;
+
+public class Cookie {
+       public static String cookie;
+       
+       public static void setCookie (String cookie) {
+               Cookie.cookie = cookie;
+       }
+       
+       public static String getCookie () {
+               return Cookie.cookie;
+       }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsLoginActivity.java b/Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsLoginActivity.java
new file mode 100644 (file)
index 0000000..5000294
--- /dev/null
@@ -0,0 +1,167 @@
+package de.android.mobiads;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.params.CoreProtocolPNames;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.StrictMode;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+
+public class MobiAdsLoginActivity extends Activity {
+       private static final String TAG = "MobiAdsLoginActivity";
+       private static final String SETCOOKIEFIELD = "Set-Cookie";
+       private StrictMode.ThreadPolicy currentPolicy;
+       
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        currentPolicy = StrictMode.getThreadPolicy();
+        StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX);
+        setContentView(R.layout.login);
+    }
+    
+    public void onClickLogin(View v) {
+       final EditText password = (EditText) findViewById(R.id.password);
+       final EditText username = (EditText) findViewById(R.id.username);
+       final HttpClient httpClient = new DefaultHttpClient();
+       String pruba = getResources().getString(R.string.url_login_web_service);
+               final HttpPost httpPost = new HttpPost(pruba);
+               HttpEntity httpEntity = null;
+               HttpResponse httpResponse = null;
+               final List<NameValuePair> formParams = new ArrayList<NameValuePair>(2);
+               
+               Cookie.setCookie("prueba");
+               
+//             httpClient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, getResources().getString(R.string.encoded_web_service));
+//             httpClient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, getResources().getString(R.string.user_agent_web_service));
+//             httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
+//             //TODO: RESTful Web Service must use JSON instead of signin array :(
+//             formParams.add(new BasicNameValuePair("signin[username]", username.getText().toString()));
+//             formParams.add(new BasicNameValuePair("signin[password]", password.getText().toString()));
+//        try {
+//                     httpEntity = new UrlEncodedFormEntity(formParams, getResources().getString(R.string.encoded_web_service));
+//                     httpPost.setEntity(httpEntity);
+//             httpResponse = httpClient.execute(httpPost);
+//             } catch (UnsupportedEncodingException e) {
+//                     Log.e(TAG, "Error while encoding POST parameters.", e);
+//                     return;
+//             } catch (ClientProtocolException e) {
+//                     Log.e(TAG, "Error while executing HTTP client connection.", e);
+//                     createErrorDialog(R.string.error_dialog_connection_error);
+//                     return;
+//             } catch (IOException e) {
+//                     Log.e(TAG, "Error while executing HTTP client connection.", e);
+//                     createErrorDialog(R.string.error_dialog_connection_error);
+//                     return;
+//             } finally {
+//                     httpClient.getConnectionManager().shutdown();
+//             }
+//        
+//        if (httpResponse != null) {
+//            switch (httpResponse.getStatusLine().getStatusCode()) {
+//             case HttpStatus.SC_OK:
+//                     String cookie = httpResponse.getLastHeader(SETCOOKIEFIELD).getValue();
+//                     if (cookie != null) {
+//                             cookie = cookie.split(";")[0];
+//                                     //Go to the next activity
+//                                     StrictMode.setThreadPolicy(currentPolicy);
+//                                     Cookie.setCookie(cookie);
+//                                     this.finish();
+//                     } else {
+//                             Log.e(TAG, "There must be a weird issue with the server because... There is not cookie!!!!");
+//                             createErrorDialog(R.string.error_dialog_connection_error);
+//                     }
+//                             break;
+//                     case HttpStatus.SC_UNAUTHORIZED:
+//                             //Username or password is incorrect
+//                             createErrorDialog(R.string.error_dialog_userpwd_error);
+//                             break;
+//                     case HttpStatus.SC_BAD_REQUEST:
+//                             //What the heck are you doing?
+//                             createErrorDialog(R.string.error_dialog_userpwd_error);
+//                             break;
+//                     default:
+//                             Log.e(TAG, "Error while retrieving the HTTP status line.");
+//                             createErrorDialog(R.string.error_dialog_connection_error);
+//                             break;
+//            }                
+//        } 
+//        else {
+//             Log.e(TAG, "No response? This should never have happened.");
+//             createErrorDialog(R.string.error_dialog_connection_error);
+//        }
+    }
+    
+    public void onClickLocalAds(View v) {
+       Intent intent = new Intent("android.intent.action.MOBIADSLIST").
+                               setComponent(new ComponentName("de.android.mobiads", "de.android.mobiads.list.MobiAdsListActivity"));
+               this.startActivity(intent);
+    }
+
+    private void createErrorDialog(int title) {
+        DialogFragment newFragment = ErrorDialogFragment.newInstance(title);
+        newFragment.show(getFragmentManager(), "errorDialog");
+    }
+    
+    public void doPositiveClick() {
+       StrictMode.setThreadPolicy(currentPolicy);
+       finish();
+    }
+
+    public void doNegativeClick() {
+
+    }
+    
+    public static class ErrorDialogFragment extends DialogFragment {
+       
+       public static ErrorDialogFragment newInstance(int title) {
+               ErrorDialogFragment frag = new ErrorDialogFragment();
+               Bundle args = new Bundle();
+               
+               args.putInt("title", title);
+               frag.setArguments(args);
+            
+               return frag;
+               }
+
+               @Override
+               public Dialog onCreateDialog(Bundle savedInstanceState) {
+                       int title = getArguments().getInt("title");
+
+                       return new AlertDialog.Builder(getActivity())
+                                               .setIcon(android.R.drawable.ic_dialog_alert)
+                                               .setTitle(title)
+                                               .setPositiveButton(R.string.button_ok,
+                                                               new DialogInterface.OnClickListener() {
+                                                                       public void onClick(DialogInterface dialog, int whichButton) {
+                                                                               
+                                                                       }
+                                                               }
+                                               )
+                                               .create();
+           }
+   }
+}
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsPreferences.java b/Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsPreferences.java
new file mode 100644 (file)
index 0000000..e717474
--- /dev/null
@@ -0,0 +1,55 @@
+package de.android.mobiads;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+
+public class MobiAdsPreferences extends PreferenceFragment {
+
+       @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+       
+       @Override 
+       public void onActivityCreated(Bundle savedInstanceState) {
+               super.onActivityCreated(savedInstanceState);
+        
+        CheckBoxPreference checkBoxPreference = (CheckBoxPreference) findPreference("service_started");
+        checkBoxPreference.setChecked(isMyServiceRunning());
+        checkBoxPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                       
+                       @Override
+                       public boolean onPreferenceClick(Preference preference) {
+                               CheckBoxPreference checkBoxPreference = (CheckBoxPreference) preference;
+                               if (checkBoxPreference.isChecked()) {
+                                       Intent intent = new Intent(getActivity(), MobiAdsService.class);
+                                       intent.putExtra("cookie", Cookie.getCookie());
+                                       getActivity().startService(intent);
+                               }
+                               else {
+                                       getActivity().stopService(new Intent(getActivity(), MobiAdsService.class));
+                               }
+                               return false;
+                       }
+               });
+    }
+       
+       private boolean isMyServiceRunning() {
+        ActivityManager manager = (ActivityManager) getActivity().getSystemService(Context.ACTIVITY_SERVICE);
+        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+            if (MobiAdsService.class.getName().equals(service.service.getClassName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsService.java b/Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsService.java
new file mode 100644 (file)
index 0000000..1734f60
--- /dev/null
@@ -0,0 +1,187 @@
+package de.android.mobiads;
+
+import de.android.mobiads.batch.MobiAdsBatch;
+import de.android.mobiads.list.MobiAdsLatest;
+import de.android.mobiads.list.MobiAdsLatestList;
+import de.android.mobiads.list.MobiAdsNewAdsActivity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+
+public class MobiAdsService extends Service {
+       private MobiAdsBatch mobiAdsBatch;
+       /** For showing and hiding our notification. */
+    NotificationManager notificationManager;
+       /**
+     * Command to the service to register a client, receiving callbacks
+     * from the service.  The Message's replyTo field must be a Messenger of
+     * the client where callbacks should be sent.
+     */
+    public static final int MSG_REGISTER_CLIENT = 1;
+
+    /**
+     * Command to the service to unregister a client, ot stop receiving callbacks
+     * from the service.  The Message's replyTo field must be a Messenger of
+     * the client as previously given with MSG_REGISTER_CLIENT.
+     */
+    public static final int MSG_UNREGISTER_CLIENT = 2;
+
+    /**
+     * Command to service to set a new value.  This can be sent to the
+     * service to supply a new value, and will be sent by the service to
+     * any registered clients with the new value.
+     */
+    public static final int MSG_SET_VALUE = 3;
+    
+    private LocationManager locationManager;
+    private LocationListener locationListener;
+    
+    /**
+     * Class for clients to access.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with
+     * IPC.
+     */
+    public class LocalBinder extends Binder {
+        MobiAdsService getService() {
+            return MobiAdsService.this;
+        }
+    }
+
+        
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+       final String cookie = intent.getStringExtra("cookie");
+       
+       //There should not be more than one thread using mobiAdsBatch field, see: 
+        //http://developer.android.com/guide/topics/fundamentals/services.html#LifecycleCallbacks
+        //Otherwise there could be issues about sharing this field...
+        this.mobiAdsBatch = new MobiAdsBatch(this.getResources().getString(R.string.user_agent_web_service), 
+                                                                                this.getResources().getString(R.string.encoded_web_service), this, cookie);
+        
+        Criteria criteria = new Criteria();
+        criteria.setAccuracy(Criteria.ACCURACY_FINE);
+        criteria.setAltitudeRequired(false);
+        criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
+        criteria.setBearingRequired(false);
+        criteria.setCostAllowed(false);
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
+        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+        criteria.setSpeedAccuracy(Criteria.ACCURACY_LOW);
+        criteria.setSpeedRequired(true);
+        criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
+        
+        
+        // Acquire a reference to the system Location Manager
+        this.locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
+
+        // Define a listener that responds to location updates
+        this.locationListener = new LocationListener() {
+               
+            public void onLocationChanged(Location location) {
+               // Called when a new location is found by the network location provider.
+               // This method is run by the main thread of this Dalvik process.
+               // Called when a new location is found by the network location provider.
+               MobiAdsService.this.mobiAdsBatch.makeUseOfNewLocation(location);
+            }
+
+            public void onStatusChanged(String provider, int status, Bundle extras) {
+               //1. Find out the provider state. (see Copilot.java code GPSLocationListener)
+               //2. If it is TEMPORARILY_UNAVAILABLE:
+               //2.1. locationManager.removeUpdates(locationListener); <--- Stop wasting GPS or GSM connections
+               //2.2. Launch Timer with TimerTask 30 or 60 seconds before to enable the locationManager to find out if the provider status changed.
+               //3. If OUT_OF_SERVICE
+               //3.1. locationManager.removeUpdates(locationListener); <--- Stop wasting GPS or GSM connections
+               //3.2. Launch Timer with TimerTask 30 or 60 seconds before to enable the locationManager to find out if the provider status changed.
+               //4. If AVAILABLE 
+               //   Nothing to do here.
+               //Just when we are in the second or third point we have to stop draining battery because it is useless.
+               
+            }
+
+            public void onProviderEnabled(String provider) {}
+
+            public void onProviderDisabled(String provider) {}
+          };
+
+        // Register the listener with the Location Manager to receive location updates
+        locationManager.requestLocationUpdates(0, 10, criteria, locationListener, null);
+        
+        notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+        // Display a notification about us starting.
+        int noReadCount = 0;
+        CharSequence contentText;
+        if ((noReadCount = this.mobiAdsBatch.noReadAdsCount()) == 0) {
+               contentText = getText(R.string.remote_service_content_empty_notification);
+               showNotification(0, noReadCount, contentText, MobiAdsLatestList.class);
+        }
+        else {
+               contentText = getText(R.string.remote_service_content_notification);
+               showNotification(0, noReadCount, contentText, MobiAdsLatestList.class);
+        }
+        
+        return super.onStartCommand(intent, flags, startId);
+    }
+    
+       @Override
+       public IBinder onBind(Intent intent) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+       
+       
+       @Override
+    public void onDestroy() {
+        // Cancel the persistent notification.
+               notificationManager.cancel(R.string.remote_service_title_notification);
+               
+        if (this.locationListener != null) {
+               this.locationManager.removeUpdates(this.locationListener);      
+        }
+        
+        if (this.mobiAdsBatch != null) {
+               this.mobiAdsBatch.endBatch();
+        }
+    }
+       
+       
+       /**
+     * Show a notification while this service is running.
+     */
+    public void showNotification(final int level, final int noReadAds, CharSequence contentText, Class<?> cls) {        
+       PendingIntent contentIntent = null;
+       
+       if (cls != null) {
+               Intent intent =  new Intent(this, cls);
+            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            // The PendingIntent to launch our activity if the user selects this notification
+            contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+       }
+        
+                
+        // Set the icon, scrolling text and timestamp
+        Notification.Builder notificationBuilder = new Notification.Builder(getApplicationContext()).
+                                                                                               setSmallIcon(R.drawable.wheelnotification, level).
+                                                                                                       setTicker(getText(R.string.remote_service_started_notification)).
+                                                                                                               setWhen(System.currentTimeMillis()).
+                                                                                                                       setContentText(contentText).
+                                                                                                                               setContentTitle(getText(R.string.remote_service_title_notification)).
+                                                                                                                                       setNumber(noReadAds).
+                                                                                                                                               setContentIntent(contentIntent);
+        Notification notification = notificationBuilder.getNotification();
+        notification.flags |= Notification.FLAG_NO_CLEAR;
+
+        // Send the notification.
+        // We use a string id because it is a unique number.  We use it later to cancel.
+        notificationManager.notify(R.string.remote_service_title_notification, notification);
+    }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsTabsActivity.java b/Android/MobiAdsReloaded/src/de/android/mobiads/MobiAdsTabsActivity.java
new file mode 100644 (file)
index 0000000..d40cdec
--- /dev/null
@@ -0,0 +1,197 @@
+package de.android.mobiads;
+
+import de.android.mobiads.list.MobiAdsList;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.app.ActionBar.Tab;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class MobiAdsTabsActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final ActionBar bar = getActionBar();
+        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+        bar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE);
+        bar.setTitle(getResources().getString(R.string.header_bar));
+
+        bar.addTab(bar.newTab()
+                .setText("Local Ads")
+                .setTabListener(new TabListener<MobiAdsList.MobiAdsListFragment>(
+                        this, "localads", MobiAdsList.MobiAdsListFragment.class)));
+        
+        if (Cookie.getCookie() != null || isMyServiceRunning()) {
+               bar.addTab(bar.newTab()
+                    .setText("Control Panel")
+                       .setTabListener(new TabListener<MobiAdsPreferences>(
+                                               this, "controlpanel", MobiAdsPreferences.class)));
+        }
+               
+        bar.addTab(bar.newTab()
+                       .setText("Login")
+                               .setTabListener(new Login()));
+        
+        
+        
+        if (savedInstanceState != null) {
+            bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
+        }
+    }
+    
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
+    }
+    
+    @Override
+    protected void onResume() {
+       super.onResume();
+       if ((Cookie.getCookie() != null) && (getActionBar().getTabCount() == 2)) {
+               getActionBar().addTab(getActionBar().newTab()
+                               .setText("Control Panel")
+                                       .setTabListener(new TabListener<MobiAdsPreferences>(
+                                          this, "controlpanel", MobiAdsPreferences.class)), 1);
+
+        }
+    }
+    
+    public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
+        private final Activity mActivity;
+        private final String mTag;
+        private final Class<T> mClass;
+        private final Bundle mArgs;
+        private Fragment mFragment;
+
+        public TabListener(Activity activity, String tag, Class<T> clz) {
+            this(activity, tag, clz, null);
+        }
+
+        public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
+            mActivity = activity;
+            mTag = tag;
+            mClass = clz;
+            mArgs = args;
+
+            // Check to see if we already have a fragment for this tab, probably
+            // from a previously saved state.  If so, deactivate it, because our
+            // initial state is that a tab isn't shown.
+            mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
+            if (mFragment != null && !mFragment.isDetached()) {
+                FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
+                ft.detach(mFragment);
+                ft.commit();
+            }
+        }
+
+        public void onTabSelected(Tab tab, FragmentTransaction ft) {
+            if (mFragment == null) {
+                mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
+                ft.add(android.R.id.content, mFragment, mTag);
+            } else {
+                ft.attach(mFragment);
+            }
+        }
+
+        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+            if (mFragment != null) {
+                ft.detach(mFragment);
+            }
+        }
+
+        public void onTabReselected(Tab tab, FragmentTransaction ft) {
+           //Nothing to do here.
+        }
+    }
+    
+    private class Login implements ActionBar.TabListener {
+
+               @Override
+               public void onTabSelected(Tab tab, FragmentTransaction ft) {
+                       if (Cookie.getCookie() != null) {
+                               createAlertDialog(R.string.alert_dialog_logged);
+                       }
+                       else {
+                               Intent intent = new Intent("android.intent.action.MOBIADS").
+                                               setComponent(new ComponentName("de.android.mobiads", "de.android.mobiads.MobiAdsLoginActivity"));
+                               startActivity(intent);
+                       }
+               }
+
+               @Override
+               public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+                       //Nothing to do here            
+               }
+
+               @Override
+               public void onTabReselected(Tab tab, FragmentTransaction ft) {
+                       
+                       if (Cookie.getCookie() != null) {
+                               createAlertDialog(R.string.alert_dialog_logged);
+                       }
+                       else {
+                               Intent intent = new Intent("android.intent.action.MOBIADS").
+                                               setComponent(new ComponentName("de.android.mobiads", "de.android.mobiads.MobiAdsLoginActivity"));
+                               startActivity(intent);
+                       }
+               }
+    }
+    
+    private void createAlertDialog(int title) {
+        DialogFragment newFragment = AlertDialogFragment.newInstance(title);
+        newFragment.show(getFragmentManager(), "alertDialog");
+    }
+    
+    public static class AlertDialogFragment extends DialogFragment {
+       
+       public static AlertDialogFragment newInstance(int title) {
+               AlertDialogFragment frag = new AlertDialogFragment();
+               Bundle args = new Bundle();
+               
+               args.putInt("title", title);
+               frag.setArguments(args);
+            
+               return frag;
+               }
+
+               @Override
+               public Dialog onCreateDialog(Bundle savedInstanceState) {
+                       int title = getArguments().getInt("title");
+
+                       return new AlertDialog.Builder(getActivity())
+                                               .setIcon(android.R.drawable.ic_dialog_alert)
+                                               .setTitle(title)
+                                               .setPositiveButton(R.string.button_ok,
+                                                               new DialogInterface.OnClickListener() {
+                                                                       public void onClick(DialogInterface dialog, int whichButton) {
+                                                                               
+                                                                       }
+                                                               }
+                                               )
+                                               .create();
+           }
+   }
+    
+    private boolean isMyServiceRunning() {
+        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+            if (MobiAdsService.class.getName().equals(service.service.getClassName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/batch/MobiAdsBatch.java b/Android/MobiAdsReloaded/src/de/android/mobiads/batch/MobiAdsBatch.java
new file mode 100644 (file)
index 0000000..961ca55
--- /dev/null
@@ -0,0 +1,323 @@
+package de.android.mobiads.batch;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.params.CoreProtocolPNames;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.location.Location;
+import android.net.Uri;
+import android.net.http.AndroidHttpClient;
+import android.util.Log;
+import de.android.mobiads.MobiAdsService;
+import de.android.mobiads.R;
+import de.android.mobiads.list.MobiAdsNewAdsActivity;
+import de.android.mobiads.provider.Indexer;
+
+public class MobiAdsBatch {
+       private static final String TAG = "MobiAdsBatch";
+       private static final int tasksMax = 10;
+       private final ExecutorService exec = Executors.newFixedThreadPool(tasksMax);
+       private final AndroidHttpClient httpClient;
+       private final Context context;
+       private final String mobiAdsCookie;
+       private static final String USERS_SERVER = "http://companies.mobiads.gumartinm.name/uploads/images/";
+       
+       
+       public MobiAdsBatch (String userAgent, String encoded, Context context, String cookie) {
+               this.context = context;
+               this.httpClient = AndroidHttpClient.newInstance(userAgent);
+               this.mobiAdsCookie = cookie;
+               this.httpClient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, encoded);
+               this.httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
+               
+       }
+       
+       
+       public void makeUseOfNewLocation(Location location) {
+       
+       final String latitude = Double.toString(location.getLatitude());
+       final String longitude = Double.toString(location.getLongitude());
+        final String latitudeReplace = latitude.replace(".", ",");
+        final String longitudeReplace = longitude.replace(".", ",");
+       final String URLAuth = "http://users.mobiads.gumartinm.name/userfront.php/api/" + latitudeReplace + "/" + longitudeReplace + "/gpsads.json";
+       URL url = null;
+       
+               try {
+                       //RESTful WebService
+                       url = new URL(URLAuth);
+               } catch (MalformedURLException e) {
+                       Log.e(TAG, "Error while creating a URL", e);
+                       return;
+               }
+               
+               final Batch batch = new Batch(url);
+               
+               this.exec.execute(batch);
+       }
+       
+       
+       public void endBatch() {
+               this.exec.shutdown();
+               this.httpClient.close();
+       }
+
+       
+       private class Batch implements Runnable {
+               private final URL url;
+               
+               private Batch (URL url) {
+                       this.url = url;
+               }
+
+               @Override
+               public void run() {
+                       ResponseHandler<StringBuilder> handler = new ResponseHandler<StringBuilder>() {
+                           public StringBuilder handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+                               //There could be a null as return value in case of not receiving any data from the server, 
+                               //although the HTTP connection was OK.
+                               return sortResponse(response);
+                           }
+                       };
+                       
+                       try {
+                               final HttpGet httpGet = new HttpGet();
+                               
+                               httpGet.setHeader("Cookie", MobiAdsBatch.this.mobiAdsCookie);
+                               try {
+                                       httpGet.setURI(this.url.toURI());
+                                       StringBuilder builder = httpClient.execute(httpGet, handler);
+                                       JSONTokener tokener = new JSONTokener(builder.toString());
+                                       JSONArray finalResult = new JSONArray(tokener);
+                                       Uri uriInsert = null;
+                                       
+                                       //TODO: finalResult.length() -1? Maybe I should remove the last semicolon in the JSON response.
+                                       for (int i = 0; i < (finalResult.length() -1); i++) {
+                                               JSONObject objects = finalResult.getJSONObject(i);
+                                               if ((uriInsert = updatedIndexer(objects)) != null) {
+                                                       try {
+                                                               downloadAds((String)objects.get("image"), (String) objects.get("id"));
+                                                               int noReadCount = 0;
+                                                       CharSequence contentText;
+                                                       if ((noReadCount =  MobiAdsBatch.this.noReadAdsCount()) == 0) {
+                                                               contentText = ((MobiAdsService)MobiAdsBatch.this.context).
+                                                                                                       getText(R.string.remote_service_content_empty_notification);
+                                                       }
+                                                       else {
+                                                               contentText = ((MobiAdsService)MobiAdsBatch.this.context).
+                                                                                                       getText(R.string.remote_service_content_notification);
+                                                       }
+                                                               ((MobiAdsService)MobiAdsBatch.this.context).
+                                                                               showNotification(0, noReadCount, contentText, MobiAdsNewAdsActivity.class);
+
+                                                       } catch (Throwable e1) {
+                                                               //In case of any error, remove the index database and the file
+                                                               //or chunk successfully stored before the error.
+                                                               try {
+                                                                       MobiAdsBatch.this.context.getContentResolver().delete(uriInsert, null, null);
+                                                                       MobiAdsBatch.this.context.deleteFile((String) objects.get("id"));
+                                                               } catch (Throwable e2) {
+                                                                       // Log this exception. The original exception (if there is one) is more
+                                                                       // important and will be thrown to the caller.
+                                                                       Log.w(TAG, "Error removing content after an exception.", e2);
+                                                               }
+                                                               
+                                                               //Besides throw the original exception.
+                                                               if (e1 instanceof Error) {
+                                                                       throw (Error) e1;
+                                                               }
+                                                               if (e1 instanceof RuntimeException) {
+                                                                       throw (RuntimeException) e1;
+                                                               }
+                                                               if (e1 instanceof IOException) {
+                                                                       throw (IOException) e1;
+                                                               }
+                                                               //throwing Throwable. At least the original exception is not lost :/
+                                                               throw new UndeclaredThrowableException(e1);
+                                                       }
+                                               }
+                                               
+                                       }
+                               } catch (URISyntaxException e) {
+                                       Log.e(TAG, "Error while creating URI from URL.", e);
+                               } catch (ClientProtocolException e) {
+                                       Log.e(TAG, "Error while executing HTTP client connection.", e);
+                               } catch (UnsupportedEncodingException e) {
+                                       Log.e(TAG, "Error  InputStreamReader.", e);
+                               } catch (IOException e) {
+                                       Log.e(TAG, "Error while executing HTTP client connection.", e);
+                               } catch (JSONException e) {
+                                       Log.e(TAG, "Error while parsing JSON response.", e);
+                               }
+                       } catch (Throwable e) {
+                               //Not sure if it is "legal" to catch Throwable...
+                               Log.e(TAG, "Caught exception, something went wrong", e);
+                       }
+               }
+               
+               public StringBuilder sortResponse(HttpResponse httpResponse) throws IOException {
+                       StringBuilder builder = null;
+                       
+                       if (httpResponse != null) {
+                               switch (httpResponse.getStatusLine().getStatusCode()) {
+                               case HttpStatus.SC_OK:
+                                       //OK
+                                       HttpEntity entity = httpResponse.getEntity();
+                                       if (entity != null) {
+                                               //outside try/catch block.
+                                               //In case of exception it is thrown, otherwise instream will not be null and we get into the try/catch block.
+                                               InputStreamReader instream = new InputStreamReader(entity.getContent()/*, entity.getContentEncoding().getValue()*/);
+                                               try {
+                                                       BufferedReader reader = new BufferedReader(instream);
+                                                       builder = new StringBuilder();
+                                                       String currentLine = null;
+                                                       currentLine = reader.readLine();
+                                                       while (currentLine != null) {
+                                                               builder.append(currentLine).append("\n");
+                                                               currentLine = reader.readLine();
+                                                       }
+                                               } finally {
+                                                       //instream will never be null in case of reaching this code, cause it was initialized outside try/catch block.
+                                                       //In case of exception here, it is thrown to the upper layer.
+                                                       instream.close();
+                                               }
+                                       }
+                                       break;
+                               case HttpStatus.SC_UNAUTHORIZED:
+                                       //ERROR IN USERNAME OR PASSWORD
+                                       throw new SecurityException("Unauthorized access: error in username or password.");
+                               case HttpStatus.SC_BAD_REQUEST:
+                                       //WHAT THE HECK ARE YOU DOING?
+                                       throw new IllegalArgumentException("Bad request.");
+                               default:
+                                       throw new IllegalArgumentException("Error while retrieving the HTTP status line.");
+                               }
+                       }
+                       return builder; 
+               }
+               
+               public void downloadAds(String image, String path)
+                               throws MalformedURLException, URISyntaxException, FileNotFoundException, IOException {
+                       final HttpGet httpGet = new HttpGet();
+                       final String URLAd = USERS_SERVER + image;
+                       HttpResponse httpResponse = null;
+                       URL url = null;
+                       OutputStream outputStream = null;
+                       
+                       try {
+                               url = new URL(URLAd);
+                               httpGet.setURI(url.toURI());
+                               //By default max 2 connections at the same time per host.
+                               //and infinite time out (we could wait here forever if we do not use a timeout see: 2.1. Connection parameters
+                               //http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html) The same for execute with handler.
+                               httpResponse = MobiAdsBatch.this.httpClient.execute(httpGet);
+                               
+                               if (httpResponse != null) {
+                                       switch (httpResponse.getStatusLine().getStatusCode()) {
+                                       case HttpStatus.SC_OK:
+                                               outputStream = MobiAdsBatch.this.context.openFileOutput(path, Context.MODE_PRIVATE);
+                                               try {
+                                                       httpResponse.getEntity().writeTo(outputStream);
+                                               } finally {
+                                                       //Closing the outputStream
+                                                       outputStream.close();
+                                               }
+                                               break;
+                                       case HttpStatus.SC_UNAUTHORIZED:
+                                               //ERROR IN USERNAME OR PASSWORD
+                                               throw new SecurityException("Unauthorized access: error in username or password.");
+                                       case HttpStatus.SC_BAD_REQUEST:
+                                               //WHAT THE HECK ARE YOU DOING?
+                                               throw new IllegalArgumentException("Bad request.");
+                                       default:
+                                               throw new IllegalArgumentException("Error while retrieving the HTTP status line.");
+                                       }
+                               }
+                       } finally {
+                               try {
+                                       if (httpResponse != null) {
+                                               HttpEntity entity = httpResponse.getEntity();
+                                               if (entity != null) {
+                                                       entity.consumeContent();
+                                               }
+                                       }
+                               } catch (IOException e) {
+                                       // Log this exception. The original exception (if there is one) is more
+                                       // important and will be thrown to the caller. See: {@link AbstractHttpClient}
+                                       Log.w("Error consuming content after an exception.", e);
+                               }
+                       }
+               }
+               
+               public Uri updatedIndexer (JSONObject objects) throws JSONException {
+                       Uri updated = null;
+                       Uri uri = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer" + "/idad/" + objects.get("id"));
+                       
+                       synchronized (MobiAdsBatch.this) {
+                               //getContentResolver().query method never returns Cursor with null value.
+                               //TODO: review my code in order to find more cases like this. :(
+                               //Be aware with the RunTimeExceptions. Apparently Java needs try/catch blocks in everywhere...
+                               //TODO this method outside updatedIndexer. It is all about semantic and good design. No time sorry...
+                               //Open an issue about improving my code some day...
+                               Cursor cursor = MobiAdsBatch.this.context.getContentResolver().query(uri, null, null, null, null);
+                               try {
+                                       if (!cursor.moveToFirst()) {
+                                               //If the advertisement was not stored on the database
+                                               ContentValues values = new ContentValues();
+                                               values.put(Indexer.Index.COLUMN_NAME_ID_AD, new Integer((String) objects.get("id")));
+                                               values.put(Indexer.Index.COLUMN_NAME_PATH, (String) objects.get("id"));
+                                               values.put(Indexer.Index.COLUMN_NAME_TEXT, (String) objects.get("text"));
+                                               values.put(Indexer.Index.COLUMN_NAME_URL, (String) objects.get("link"));
+                                               values.put(Indexer.Index.COLUMN_NAME_AD_NAME, (String) objects.get("adname"));
+                                               values.put(Indexer.Index.COLUMN_NAME_IS_READ, new Integer(0));
+                                               //This method may throw SQLiteException (as a RunTimeException). So, without a try/catch block
+                                               //there could be a leaked cursor...
+                                               //TODO: review code looking for more cases like this one...
+                                               updated = MobiAdsBatch.this.context.getContentResolver().insert(Indexer.Index.CONTENT_ID_URI_BASE, values);
+                                       }
+                               } finally {
+                                       cursor.close();
+                               }
+                       }
+                       return updated;
+               }
+       }
+       
+       
+       public int noReadAdsCount() {
+               Uri uri = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer" + "/isRead/");
+               
+               Cursor cursor = MobiAdsBatch.this.context.getContentResolver().query(uri, null, null, null, null);
+               int count = 0; 
+               
+               try {
+                       count = cursor.getCount();
+               } finally {
+                       cursor.close();
+               }
+               
+               return count;
+       }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/list/AdsEntry.java b/Android/MobiAdsReloaded/src/de/android/mobiads/list/AdsEntry.java
new file mode 100644 (file)
index 0000000..f5492e2
--- /dev/null
@@ -0,0 +1,59 @@
+package de.android.mobiads.list;
+
+import android.graphics.Bitmap;
+
+
+/**
+ * Encapsulates information about an ads entry
+ */
+public final class AdsEntry {
+       
+       private final String title;
+       private final String text;
+       private final Bitmap icon;
+       private final int idAd;
+       private final String URL;
+
+       public AdsEntry(final String title, final String text, final Bitmap icon, final int idAd, final String URL) {
+               this.title = title;
+               this.text = text;
+               this.icon = icon;
+               this.idAd = idAd;
+               this.URL = URL;
+       }
+
+       /**
+        * @return Title of ads entry
+        */
+       public String getTitle() {
+               return title;
+       }
+
+       /**
+        * @return Text of ads entry
+        */
+       public String getText() {
+               return text;
+       }
+
+       /**
+        * @return Icon of this ads entry
+        */
+       public Bitmap getIcon() {
+               return icon;
+       }
+
+       /**
+        * @return Ad unique identifier of this ads entry
+        */
+       public int getIdAd() {
+               return idAd;
+       }
+       
+       /**
+        * @return URL matching this ad.
+        */
+       public String getURL() {
+               return URL;
+       }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/list/AdsEntryAdapter.java b/Android/MobiAdsReloaded/src/de/android/mobiads/list/AdsEntryAdapter.java
new file mode 100644 (file)
index 0000000..0d4dffb
--- /dev/null
@@ -0,0 +1,94 @@
+package de.android.mobiads.list;
+
+import android.widget.ArrayAdapter;
+import de.android.mobiads.R;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Adapts AdsEntry objects onto views for lists
+ */
+public final class AdsEntryAdapter extends ArrayAdapter<AdsEntry> {
+
+       private final int adsItemLayoutResource;
+
+       public AdsEntryAdapter(final Context context, final int adsItemLayoutResource) {
+               super(context, 0);
+               this.adsItemLayoutResource = adsItemLayoutResource;
+       }
+
+       @Override
+       public View getView(final int position, final View convertView, final ViewGroup parent) {
+               
+               // We need to get the best view (re-used if possible) and then
+               // retrieve its corresponding ViewHolder, which optimizes lookup efficiency
+               final View view = getWorkingView(convertView);
+               final ViewHolder viewHolder = getViewHolder(view);
+               final AdsEntry entry = getItem(position);
+               
+               // Setting the text view
+               viewHolder.titleView.setText(entry.getTitle());
+               
+               viewHolder.textView.setText(entry.getText());
+               
+               // Setting image view
+               viewHolder.imageView.setImageBitmap(entry.getIcon());
+               
+               return view;
+       }
+
+       private View getWorkingView(final View convertView) {
+               // The workingView is basically just the convertView re-used if possible
+               // or inflated new if not possible
+               View workingView = null;
+               
+               if(null == convertView) {
+                       final Context context = getContext();
+                       final LayoutInflater inflater = (LayoutInflater)context.getSystemService
+                     (Context.LAYOUT_INFLATER_SERVICE);
+                       
+                       workingView = inflater.inflate(adsItemLayoutResource, null);
+               } else {
+                       workingView = convertView;
+               }
+               
+               return workingView;
+       }
+       
+       private ViewHolder getViewHolder(final View workingView) {
+               // The viewHolder allows us to avoid re-looking up view references
+               // Since views are recycled, these references will never change
+               final Object tag = workingView.getTag();
+               ViewHolder viewHolder = null;
+               
+               
+               if(null == tag || !(tag instanceof ViewHolder)) {
+                       viewHolder = new ViewHolder();
+                       
+                       viewHolder.titleView = (TextView) workingView.findViewById(R.id.ads_entry_title);
+                       viewHolder.textView = (TextView) workingView.findViewById(R.id.ads_entry_text);
+                       viewHolder.imageView = (ImageView) workingView.findViewById(R.id.ads_entry_icon);
+                       
+                       workingView.setTag(viewHolder);
+                       
+               } else {
+                       viewHolder = (ViewHolder) tag;
+               }
+               
+               return viewHolder;
+       }
+       
+       /**
+        * ViewHolder allows us to avoid re-looking up view references
+        * Since views are recycled, these references will never change
+        */
+       private static class ViewHolder {
+               public TextView titleView;
+               public TextView textView;
+               public ImageView imageView;
+       }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsLatest.java b/Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsLatest.java
new file mode 100644 (file)
index 0000000..767a3e7
--- /dev/null
@@ -0,0 +1,557 @@
+package de.android.mobiads.list;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import de.android.mobiads.MobiAdsService;
+import de.android.mobiads.R;
+import de.android.mobiads.provider.Indexer;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.AsyncTaskLoader;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.TextView;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.SearchView.OnQueryTextListener;
+
+public class MobiAdsLatest extends Activity {
+
+       @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.latest_ads);
+    }
+       
+       public static class MobiAdsListFragment extends ListFragment implements OnQueryTextListener, 
+               LoaderManager.LoaderCallbacks<List<AdsEntry>> {
+               AdsEntryAdapter mAdapter;
+               // If non-null, this is the current filter the user has provided.
+               String mCurFilter;
+
+
+
+               @Override
+        public void onActivityCreated(Bundle savedInstanceState) {
+            super.onActivityCreated(savedInstanceState);
+
+                       ListView listView = getListView();
+
+                       listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+
+                       listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+                               @Override
+                               public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+                                       final int checkedCount = getListView().getCheckedItemCount();
+                                       switch (checkedCount) {
+                                       case 0:
+                                               mode.setSubtitle(null);
+                                                       break;
+                                               case 1:
+                                                       mode.setSubtitle("One item selected");
+                                                       break;
+                                               default:
+                                                       mode.setSubtitle("" + checkedCount + " items selected");
+                                                       break;
+                                               }
+                                       }
+
+                                       
+                               @Override
+                               public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                                       // Respond to clicks on the actions in the CAB
+                                       switch (item.getItemId()) {
+                                       case R.id.menuadsremove:
+                                               SparseBooleanArray itemsPositions = getListView().getCheckedItemPositions();
+                                               Collection<AdsEntry> aux = new ArrayList<AdsEntry>(mAdapter.getCount());
+                                               for (int i=0; i< itemsPositions.size(); i++) {
+                                                       if (itemsPositions.valueAt(i)) {
+                                                               aux.add(mAdapter.getItem(itemsPositions.keyAt(i)));
+                                                       }
+                                               }
+                                               if (!aux.isEmpty()) {
+                                                       for(final AdsEntry entry : aux) {
+                                                               removeAd(entry);
+                                                       }
+                                               }
+                                               mode.finish(); // Action picked, so close the CAB
+                                               return true;
+                                       default:
+                                               return false;
+                                       }
+                               }
+
+                               @Override
+                               public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                                       MenuInflater inflater = mode.getMenuInflater();
+                                       inflater.inflate(R.menu.menuads, menu);
+                                       final int checkedCount = getListView().getCheckedItemCount();
+                                       switch (checkedCount) {
+                                       case 0:
+                                               mode.setSubtitle(null);
+                                               break;
+                                       case 1:
+                                               mode.setSubtitle("One item selected");
+                                               break;
+                                       default:
+                                               mode.setSubtitle("" + checkedCount + " items selected");
+                                               break;
+                                       }
+                                       return true;
+                               }
+
+                               @Override
+                               public void onDestroyActionMode(ActionMode mode) {
+                                       //TODO: Save state (checked items) in orde to keep them when coming back from
+                                       //the home screen.
+                               }
+
+                               @Override
+                               public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                                       // Here you can perform updates to the CAB due to
+                                       // an invalidate() request
+                                       return false;
+                               }
+                       });
+
+
+                       listView.setOnItemClickListener(new OnItemClickListener() {
+
+                               @Override
+                               public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                                       Uri uri = Uri.parse(mAdapter.getItem(position).getURL());
+                                       startActivity(new Intent(Intent.ACTION_VIEW, uri));
+                               }
+                       });
+
+                       setEmptyText("No downloaded Ads.");
+
+                       // We have a menu item to show in action bar.
+                       setHasOptionsMenu(true);      
+
+
+                       mAdapter = new AdsEntryAdapter(getActivity(), R.layout.ads_entry_list_item);
+
+                       setListAdapter(mAdapter);
+                       // Start out with a progress indicator.
+                       setListShown(false);
+
+                       // Prepare the loader.  Either re-connect with an existing one,
+                       // or start a new one.                  
+                       //TODO: reload just if there are changes in the data base :/
+                       //getLoaderManager().initLoader(0, null, this);
+                       getLoaderManager().restartLoader(0, null, this);
+               }
+
+               private void removeAd(AdsEntry entry){
+                       int idAd = entry.getIdAd();
+                       Uri uriDelete = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer" + "/idad/" + idAd);
+
+                       getActivity().getContentResolver().delete(uriDelete, null, null);
+                       mAdapter.remove(entry);
+                       mAdapter.notifyDataSetChanged();
+               }
+
+               @Override 
+               public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+                       // Place an action bar item for searching.
+                       MenuItem item = menu.add("Search");
+                       item.setIcon(android.R.drawable.ic_menu_search);
+                       item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                       SearchView sv = new SearchView(getActivity());
+                       sv.setOnQueryTextListener(this);
+                       item.setActionView(sv);
+               }
+
+               @Override
+               public Loader<List<AdsEntry>> onCreateLoader(int id, Bundle args) {
+                       // This is called when a new Loader needs to be created.  This
+                       // sample only has one Loader with no arguments, so it is simple.
+                       return new AdsListLoader(getActivity());
+               }
+
+               @Override
+               public void onLoadFinished(Loader<List<AdsEntry>> loader, List<AdsEntry> data) {
+                       mAdapter.setData(data);
+
+                       // The list should now be shown.
+                       if (isResumed()) {
+                               setListShown(true);
+                       } else {
+                               setListShownNoAnimation(true);
+                       }
+
+                       if (isMyServiceRunning()) {
+                               showNotification(0, 0, getText(R.string.remote_service_content_empty_notification));
+                       }
+               }
+
+               @Override
+               public void onLoaderReset(Loader<List<AdsEntry>> loader) {
+                       mAdapter.setData(null);
+               }
+
+               @Override
+               public boolean onQueryTextSubmit(String query) {
+                       // Don't care about this.
+                       return true;
+               }
+
+               @Override
+               public boolean onQueryTextChange(String newText) {
+                       // Called when the action bar search text has changed.  Update
+                       // the search filter, and restart the loader to do a new query
+                       // with this filter.
+                       mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+                       getLoaderManager().restartLoader(0, null, this);
+                       return true;
+               }
+
+               private boolean isMyServiceRunning() {
+                       ActivityManager manager = (ActivityManager) getActivity().getSystemService(Context.ACTIVITY_SERVICE);
+                       for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+                               if (MobiAdsService.class.getName().equals(service.service.getClassName())) {
+                                       return true;
+                               }
+                       }
+                       return false;
+               }
+
+               public void showNotification(final int level, final int noReadAds, CharSequence contentText) {        
+                       NotificationManager notificationManager = (NotificationManager)getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
+
+                       // Set the icon, scrolling text and timestamp
+                       Notification.Builder notificationBuilder = new Notification.Builder(getActivity().getApplicationContext()).
+                                               setSmallIcon(R.drawable.wheelnotification, level).
+                                                       setTicker(getText(R.string.remote_service_started_notification)).
+                                                               setWhen(System.currentTimeMillis()).
+                                                                       setContentText(contentText).
+                                                                               setContentTitle(getText(R.string.remote_service_title_notification)).
+                                                                                       setNumber(noReadAds);
+                       Notification notification = notificationBuilder.getNotification();
+                       notification.flags |= Notification.FLAG_NO_CLEAR;
+
+                       // Send the notification.
+                       // We use a string id because it is a unique number.  We use it later to cancel.
+                       notificationManager.notify(R.string.remote_service_title_notification, notification);
+               }
+       }
+
+
+       public static class AdsEntryAdapter extends ArrayAdapter<AdsEntry> {
+               private final int adsItemLayoutResource;
+
+               public AdsEntryAdapter(final Context context, final int adsItemLayoutResource) {
+                       super(context, 0);
+                       this.adsItemLayoutResource = adsItemLayoutResource;
+               }
+
+               public void setData(List<AdsEntry> data) {
+                       clear();
+                       if (data != null) {
+                               addAll(data);
+                       }
+               }
+
+               /**
+                * Populate new items in the list.
+                */
+               @Override
+               public View getView(final int position, final View convertView, final ViewGroup parent) {
+
+                       // We need to get the best view (re-used if possible) and then
+                       // retrieve its corresponding ViewHolder, which optimizes lookup efficiency
+                       final View view = getWorkingView(convertView);
+                       final ViewHolder viewHolder = getViewHolder(view);
+                       final AdsEntry entry = getItem(position);
+
+                       // Setting the text view
+                       viewHolder.titleView.setText(entry.getTitle());
+
+                       viewHolder.textView.setText(entry.getText());
+
+                       // Setting image view
+                       viewHolder.imageView.setImageBitmap(entry.getIcon());
+
+
+                       return view;
+               }
+
+               private View getWorkingView(final View convertView) {
+                       // The workingView is basically just the convertView re-used if possible
+                       // or inflated new if not possible
+                       View workingView = null;
+
+                       if(null == convertView) {
+                               final Context context = getContext();
+                               final LayoutInflater inflater = (LayoutInflater)context.getSystemService
+                                               (Context.LAYOUT_INFLATER_SERVICE);
+
+                               workingView = inflater.inflate(adsItemLayoutResource, null);
+                       } else {
+                               workingView = convertView;
+                       }
+
+                       return workingView;
+               }
+
+               private ViewHolder getViewHolder(final View workingView) {
+                       // The viewHolder allows us to avoid re-looking up view references
+                       // Since views are recycled, these references will never change
+                       final Object tag = workingView.getTag();
+                       ViewHolder viewHolder = null;
+
+
+                       if(null == tag || !(tag instanceof ViewHolder)) {
+                               viewHolder = new ViewHolder();
+
+                               viewHolder.titleView = (TextView) workingView.findViewById(R.id.ads_entry_title);
+                               viewHolder.textView = (TextView) workingView.findViewById(R.id.ads_entry_text);
+                               viewHolder.imageView = (ImageView) workingView.findViewById(R.id.ads_entry_icon);
+
+                               workingView.setTag(viewHolder);
+
+                       } else {
+                               viewHolder = (ViewHolder) tag;
+                       }
+
+                       return viewHolder;
+               }
+
+               /**
+                * ViewHolder allows us to avoid re-looking up view references
+                * Since views are recycled, these references will never change
+                */
+               private static class ViewHolder {
+                       public TextView titleView;
+                       public TextView textView;
+                       public ImageView imageView;
+               }
+       }
+
+       /**
+        * Encapsulates information about an ads entry
+        */
+       public static class AdsEntry {
+
+               private final String title;
+               private final String text;
+               private final Bitmap icon;
+               private final int idAd;
+               private final String URL;
+
+               public AdsEntry(final String title, final String text, final Bitmap icon, final int idAd, final String URL) {
+                       this.title = title;
+                       this.text = text;
+                       this.icon = icon;
+                       this.idAd = idAd;
+                       this.URL = URL;
+               }
+
+               /**
+                * @return Title of ads entry
+                */
+               public String getTitle() {
+                       return title;
+               }
+
+               /**
+                * @return Text of ads entry
+                */
+               public String getText() {
+                       return text;
+               }
+
+               /**
+                * @return Icon of this ads entry
+                */
+               public Bitmap getIcon() {
+                       return icon;
+               }
+
+               /**
+                * @return Ad unique identifier of this ads entry
+                */
+               public int getIdAd() {
+                       return idAd;
+               }
+
+               /**
+                * @return URL matching this ad.
+                */
+               public String getURL() {
+                       return URL;
+               }
+       }
+       
+       /**
+        * A custom Loader that loads all of the installed applications.
+        */
+       public static class AdsListLoader extends AsyncTaskLoader<List<AdsEntry>> {
+               private static final String TAG = "AdsListLoader";
+               List<AdsEntry> mApps;
+
+               public AdsListLoader(Context context) {
+                       super(context);
+               }
+
+               /**
+                * This is where the bulk of our work is done.  This function is
+                * called in a background thread and should generate a new set of
+                * data to be published by the loader.
+                */
+               @Override 
+               public List<AdsEntry> loadInBackground() {
+                       // Create corresponding array of entries and load their labels.
+                       List<AdsEntry> entries = getAdsEntries();
+
+                       return entries;
+               }
+
+               private List<AdsEntry> getAdsEntries() {
+                       final List<AdsEntry> entries = new ArrayList<AdsEntry>();
+                       final Uri uri = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer");
+                       final ContentValues values = new ContentValues();
+       
+                       Cursor cursor = getContext().getContentResolver().query(uri, null, null, null, null);
+                       try {
+                               if (cursor.moveToFirst()) {
+                                       do {
+                                               values.clear();
+                                               Bitmap bitMap = null;
+                                               FileInputStream file = null;
+                                               try {
+                                                       file = getContext().openFileInput(cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_PATH)));
+                                                       bitMap = BitmapFactory.decodeStream(file);
+                                               } catch (FileNotFoundException e) {
+                                                       //Giving more chances to other ads
+                                                       continue;
+                                               } catch (IllegalArgumentException e) {
+                                                       //Giving more chances to other ads
+                                                       continue;
+                                               }
+                                               finally {
+                                                       if (file != null) {
+                                                               try {
+                                                                       file.close();
+                                                               } catch (IOException e) {
+                                                                       Log.w(TAG, "Error while closing image file.");
+                                                               }
+                                                       }
+                                               }
+                                               entries.add(new AdsEntry(cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_AD_NAME)), 
+                                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_TEXT)), bitMap,
+                                                               cursor.getInt(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_ID_AD)),
+                                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_URL))));                        
+                                               if (cursor.getInt(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_IS_READ)) == 0)
+                                               {
+                                                       values.put(Indexer.Index.COLUMN_NAME_IS_READ, new Integer(1));
+                                                       Uri uriUpdate = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer/" + 
+                                                                                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index._ID)));
+                                                       getContext().getContentResolver().update(uriUpdate, values, null, null);
+                                               }
+                                       }while (cursor.moveToNext());
+                               } 
+                       }finally {
+                               cursor.close();
+                       }
+
+                       return entries;
+               }
+
+               /**
+                * Called when there is new data to deliver to the client.  The
+                * super class will take care of delivering it; the implementation
+                * here just adds a little more logic.
+                */
+               @Override 
+               public void deliverResult(List<AdsEntry> apps) {
+                       mApps = apps;
+
+                       if (isStarted()) {
+                               // If the Loader is currently started, we can immediately
+                               // deliver its results.
+                               super.deliverResult(apps);
+                       }
+               }
+
+               /**
+                * Handles a request to start the Loader.
+                */
+               @Override 
+               protected void onStartLoading() {
+                       if (mApps != null) {
+                               // If we currently have a result available, deliver it
+                               // immediately.
+                               deliverResult(mApps);
+                       }
+
+                       if (takeContentChanged() || mApps == null) {
+                               // If the data has changed since the last time it was loaded
+                               // or is not currently available, start a load.
+                               forceLoad();
+                       }
+               }
+
+               /**
+                * Handles a request to cancel a load.
+                */
+               @Override 
+               public void onCanceled(List<AdsEntry> apps) {
+                       super.onCanceled(apps);
+
+                       // At this point we can release the resources associated with 'apps'
+                       // if needed.
+               }
+
+               /**
+                * Handles a request to completely reset the Loader.
+                */
+               @Override 
+               protected void onReset() {
+                       super.onReset();
+
+                       // Ensure the loader is stopped
+                       onStopLoading();
+
+                       // At this point we can release the resources associated with 'apps'
+                       // if needed.
+                       if (mApps != null) {
+                               mApps = null;
+                       }
+               }
+       }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsLatestList.java b/Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsLatestList.java
new file mode 100644 (file)
index 0000000..3796d71
--- /dev/null
@@ -0,0 +1,144 @@
+package de.android.mobiads.list;
+
+import java.util.List;
+import de.android.mobiads.MobiAdsService;
+import de.android.mobiads.R;
+import de.android.mobiads.list.MobiAdsLatest.AdsEntry;
+import de.android.mobiads.list.MobiAdsLatest.AdsEntryAdapter;
+import de.android.mobiads.list.MobiAdsLatest.AdsListLoader;
+import android.app.ActivityManager;
+import android.app.ListActivity;
+import android.app.LoaderManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+public class MobiAdsLatestList extends ListActivity implements LoaderManager.LoaderCallbacks<List<AdsEntry>> {
+       AdsEntryAdapter mAdapter;
+       
+       @Override
+       public void onResume() {
+               super.onResume();
+               
+                mAdapter = new AdsEntryAdapter(this, R.layout.ads_entry_list_item);
+                setListAdapter(mAdapter);
+                   
+                getListView().setTextFilterEnabled(true);
+                       
+                // Tell the list view to show one checked/activated item at a time.
+                getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+               
+                getListView().setOnItemClickListener(new OnItemClickListener() {
+
+                       @Override
+                       public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                               Uri uri = Uri.parse(mAdapter.getItem(position).getTitle());
+                               startActivity(new Intent(Intent.ACTION_VIEW, uri));
+                       }
+                });
+                
+                registerForContextMenu(getListView());
+           
+                //TODO: stop using onResume and to use a broadcast from the service about changes in database.
+                getLoaderManager().restartLoader(0, null, this);
+        }
+
+       @Override
+       public Loader<List<AdsEntry>> onCreateLoader(int id, Bundle args) {
+               // This is called when a new Loader needs to be created.  This
+               // sample only has one Loader with no arguments, so it is simple.
+               return new AdsListLoader(this);
+       }
+
+       @Override
+       public void onLoadFinished(Loader<List<AdsEntry>> loader,
+                       List<AdsEntry> data) {
+               mAdapter.setData(data);
+
+
+               //TODO: this should be done with a broadcast to our service. The service should be the only one
+               //showing notifications... :/
+               if (isMyServiceRunning()) {
+                       showNotification(0, 0, getText(R.string.remote_service_content_empty_notification));
+               }
+               
+       }
+
+       @Override
+       public void onLoaderReset(Loader<List<AdsEntry>> loader) {
+               mAdapter.setData(null);
+       }
+       
+       @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, v, menuInfo);
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.menuads, menu);
+    }
+       
+
+       @Override
+       public boolean onContextItemSelected(MenuItem item) {
+               AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+               switch (item.getItemId()) {
+                       case R.id.menuadsremove:
+                               removeAd(info.position);
+                               return true;
+                       default:
+                               return super.onContextItemSelected(item);
+               }
+       }
+       
+       public void removeAd(int position){
+               AdsEntry entry = mAdapter.getItem(position);
+               int idAd = entry.getIdAd();
+               Uri uriDelete = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer" + "/idad/" + idAd);
+               
+               getContentResolver().delete(uriDelete, null, null);
+               mAdapter.remove(entry);
+               mAdapter.notifyDataSetChanged();
+       }
+       
+       private boolean isMyServiceRunning() {
+               ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+               for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+                       if (MobiAdsService.class.getName().equals(service.service.getClassName())) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+       
+       public void showNotification(final int level, final int noReadAds, CharSequence contentText) {        
+               NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+
+               // Set the icon, scrolling text and timestamp
+               Notification.Builder notificationBuilder = new Notification.Builder(getApplicationContext()).
+                                       setSmallIcon(R.drawable.wheelnotification, level).
+                                               setTicker(getText(R.string.remote_service_started_notification)).
+                                                       setWhen(System.currentTimeMillis()).
+                                                               setContentText(contentText).
+                                                                       setContentTitle(getText(R.string.remote_service_title_notification)).
+                                                                               setNumber(noReadAds);
+               Notification notification = notificationBuilder.getNotification();
+               notification.flags |= Notification.FLAG_NO_CLEAR;
+
+               // Send the notification.
+               // We use a string id because it is a unique number.  We use it later to cancel.
+               notificationManager.notify(R.string.remote_service_title_notification, notification);
+       }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsList.java b/Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsList.java
new file mode 100644 (file)
index 0000000..a33a35e
--- /dev/null
@@ -0,0 +1,641 @@
+package de.android.mobiads.list;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.app.FragmentManager;
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.AsyncTaskLoader;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.SearchView.OnQueryTextListener;
+import android.widget.TextView;
+import de.android.mobiads.MobiAdsService;
+import de.android.mobiads.R;
+import de.android.mobiads.provider.Indexer;
+
+public class MobiAdsList extends Activity {
+       
+       @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final ActionBar bar = getActionBar();
+        
+        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+        bar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE);
+        bar.setTitle(getResources().getString(R.string.header_bar));
+        
+        
+        FragmentManager fm = getFragmentManager();
+
+        // Create the list fragment and add it as our sole content.
+        if (fm.findFragmentById(android.R.id.content) == null) {
+               MobiAdsListFragment list = new MobiAdsListFragment();
+            fm.beginTransaction().add(android.R.id.content, list).commit();
+        }
+        
+        
+    }
+       
+       @Override
+       public boolean onOptionsItemSelected(MenuItem item) {
+               return super.onOptionsItemSelected(item);
+       }
+       
+       @Override
+       public boolean onPrepareOptionsMenu(Menu menu) {
+               return super.onPrepareOptionsMenu(menu);
+       }
+       
+       @Override
+       public boolean onCreateOptionsMenu(Menu menu) {
+               super.onCreateOptionsMenu(menu);
+           MenuInflater inflater = getMenuInflater();
+           inflater.inflate(R.menu.menuads, menu);
+           
+           return true;
+       }
+
+       
+       /**
+        * A custom Loader that loads all of the installed applications.
+        */
+       public static class AdsListLoader extends AsyncTaskLoader<List<AdsEntry>> {
+               private static final String TAG = "AdsListLoader";
+               List<AdsEntry> mApps;
+
+               public AdsListLoader(Context context) {
+                       super(context);
+               }
+
+               /**
+                * This is where the bulk of our work is done.  This function is
+                * called in a background thread and should generate a new set of
+                * data to be published by the loader.
+                */
+               @Override 
+               public List<AdsEntry> loadInBackground() {
+                       // Create corresponding array of entries and load their labels.
+                       List<AdsEntry> entries = getAdsEntries();
+
+                       return entries;
+               }
+
+               private List<AdsEntry> getAdsEntries() {
+                       final List<AdsEntry> entries = new ArrayList<AdsEntry>();
+                       final Uri uri = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer");
+                       final ContentValues values = new ContentValues();
+       
+                       Cursor cursor = getContext().getContentResolver().query(uri, null, null, null, null);
+                       try {
+                               if (cursor.moveToFirst()) {
+                                       do {
+                                               values.clear();
+                                               Bitmap bitMap = null;
+                                               FileInputStream file = null;
+                                               try {
+                                                       file = getContext().openFileInput(cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_PATH)));
+                                                       bitMap = BitmapFactory.decodeStream(file);
+                                               } catch (FileNotFoundException e) {
+                                                       //Giving more chances to other ads
+                                                       continue;
+                                               } catch (IllegalArgumentException e) {
+                                                       //Giving more chances to other ads
+                                                       continue;
+                                               }
+                                               finally {
+                                                       if (file != null) {
+                                                               try {
+                                                                       file.close();
+                                                               } catch (IOException e) {
+                                                                       Log.w(TAG, "Error while closing image file.");
+                                                               }
+                                                       }
+                                               }
+                                               entries.add(new AdsEntry(cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_AD_NAME)), 
+                                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_TEXT)), bitMap,
+                                                               cursor.getInt(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_ID_AD)),
+                                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_URL))));
+                                               //Esto debe ser hecho como en LoaderThrottle en una AsyncTask cuando el usuario lea el anuncio.
+                                               //Incialmente (si no leido) el anuncio tiene background gris. Cuando se ha leido tiene el background negro.
+                                               if (cursor.getInt(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_IS_READ)) == 0)
+                                               {
+                                                       values.put(Indexer.Index.COLUMN_NAME_IS_READ, new Integer(1));
+                                                       Uri uriUpdate = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer/" + 
+                                                                                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index._ID)));
+                                                       getContext().getContentResolver().update(uriUpdate, values, null, null);
+                                               }
+                                       }while (cursor.moveToNext());
+                               } 
+                       }finally {
+                               cursor.close();
+                       }
+
+                       return entries;
+               }
+
+               /**
+                * Called when there is new data to deliver to the client.  The
+                * super class will take care of delivering it; the implementation
+                * here just adds a little more logic.
+                */
+               @Override 
+               public void deliverResult(List<AdsEntry> apps) {
+                       mApps = apps;
+
+                       if (isStarted()) {
+                               // If the Loader is currently started, we can immediately
+                               // deliver its results.
+                               super.deliverResult(apps);
+                       }
+               }
+
+               /**
+                * Handles a request to start the Loader.
+                */
+               @Override 
+               protected void onStartLoading() {
+                       if (mApps != null) {
+                               // If we currently have a result available, deliver it
+                               // immediately.
+                               deliverResult(mApps);
+                       }
+
+                       if (takeContentChanged() || mApps == null) {
+                               // If the data has changed since the last time it was loaded
+                               // or is not currently available, start a load.
+                               forceLoad();
+                       }
+               }
+
+               /**
+                * Handles a request to cancel a load.
+                */
+               @Override 
+               public void onCanceled(List<AdsEntry> apps) {
+                       super.onCanceled(apps);
+
+                       // At this point we can release the resources associated with 'apps'
+                       // if needed.
+               }
+
+               /**
+                * Handles a request to completely reset the Loader.
+                */
+               @Override 
+               protected void onReset() {
+                       super.onReset();
+
+                       // Ensure the loader is stopped
+                       onStopLoading();
+
+                       // At this point we can release the resources associated with 'apps'
+                       // if needed.
+                       if (mApps != null) {
+                               mApps = null;
+                       }
+               }
+       }
+
+       public static class MobiAdsListFragment extends ListFragment implements OnQueryTextListener, 
+                                                                                                               LoaderManager.LoaderCallbacks<List<AdsEntry>> {
+               AdsEntryAdapter mAdapter;
+               // If non-null, this is the current filter the user has provided.
+               String mCurFilter;
+               
+               
+                @Override 
+                public void onActivityCreated(Bundle savedInstanceState) {
+                        super.onActivityCreated(savedInstanceState);
+                        
+                        ListView listView = getListView();
+                               
+                               listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+                               
+                               
+                               listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+                                   @Override
+                                   public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+                                       final int checkedCount = getListView().getCheckedItemCount();
+                                       switch (checkedCount) {
+                               case 0:
+                                   mode.setSubtitle(null);
+                                   break;
+                               case 1:
+                                   mode.setSubtitle("One item selected");
+                                   break;
+                               default:
+                                   mode.setSubtitle("" + checkedCount + " items selected");
+                                   break;
+                                       }
+                                   }
+
+                                   @Override
+                                   public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                                       // Respond to clicks on the actions in the CAB
+                                       switch (item.getItemId()) {
+                                           case R.id.selectedmenuads:
+                                               SparseBooleanArray itemsPositions = getListView().getCheckedItemPositions();
+                                               Collection<AdsEntry> aux = new ArrayList<AdsEntry>(mAdapter.getCount());
+                                               for (int i=0; i< itemsPositions.size(); i++) {
+                                                       if (itemsPositions.valueAt(i)) {
+                                                               aux.add(mAdapter.getItem(itemsPositions.keyAt(i)));
+                                                       }
+                                               }
+                                               if (!aux.isEmpty()) {
+                                                       for(final AdsEntry entry : aux) {
+                                                               removeAd(entry);
+                                                       }
+                                               }
+                                               mode.finish(); // Action picked, so close the CAB
+                                               return true;
+                                           default:
+                                               return false;
+                                       }
+                                   }
+
+                                   /**
+                                    * When selecting an item we remove the current action bar menu and we populate it with this one.
+                                    */
+                                   @Override
+                                   public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                                       MenuInflater inflater = mode.getMenuInflater();
+                                       inflater.inflate(R.menu.selectedmenuads, menu);
+                                       final int checkedCount = getListView().getCheckedItemCount();
+                                       switch (checkedCount) {
+                               case 0:
+                                   mode.setSubtitle(null);
+                                   break;
+                               case 1:
+                                   mode.setSubtitle("One item selected");
+                                   break;
+                               default:
+                                   mode.setSubtitle("" + checkedCount + " items selected");
+                                   break;
+                                       }
+                                       return true;
+                                   }
+
+                                   @Override
+                                   public void onDestroyActionMode(ActionMode mode) {
+                                       //TODO: Save state (checked items) in orde to keep them when coming back from
+                                       //the home screen.
+                                   }
+
+                                   @Override
+                                   public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                                       // Here you can perform updates to the CAB due to
+                                       // an invalidate() request
+                                       return false;
+                                   }
+                               });
+                               
+                               setEmptyText("No downloaded Ads.");
+
+                               // We have a menu item to show in action bar.
+                               setHasOptionsMenu(false);      
+
+
+                               mAdapter = new AdsEntryAdapter(getActivity(), R.layout.ads_entry_list_item);
+
+                               setListAdapter(mAdapter);
+                               // Start out with a progress indicator.
+                               setListShown(false);
+                }
+               
+               //TODO: Broadcast receiver from service, and stop using onResume... :/
+               @Override
+               public void onResume() {
+                       super.onResume();
+                               
+                       // Prepare the loader.  Either re-connect with an existing one,
+                       // or start a new one.                  
+                       //TODO: reload just if there are changes in the data base :/ What means: broadcast receiver from service...
+                       //getLoaderManager().initLoader(0, null, this);
+                       getLoaderManager().restartLoader(0, null, this);
+               }
+               
+               @Override 
+               public void onListItemClick(ListView l, View v, int position, long id) {
+                       //Mirar LoaderThrottle. Aqui tengo que lanzar una aynctask y es aquí y no antes cuando tengo que
+                       //indicar que el anuncio ha sido leido. Es decir actualizar la base datos.
+                       //Ademas en AdsEntry necesito un nuevo campo que este asociado al campo de leido/no leido en la base de datos
+                       //y si no está leido cambiar el background del anuncio. Si no se ha leido ponerlo en gris y si sí se ha leído
+                       //ponerlo en negro (lo de por defecto)
+                       //Supone: 1 cambiar el getAdsEntries() para que no actualice ahí la base datos si no aquí, 2 añadir al AdsEntry
+                       //un nuevo campo indicando si se ha leido ya o no. :/
+                       Uri uri = Uri.parse(mAdapter.getItem(position).getURL());
+                       startActivity(new Intent(Intent.ACTION_VIEW, uri));
+        }
+               
+               private void removeAd(AdsEntry entry){
+               int idAd = entry.getIdAd();
+               Uri uriDelete = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer" + "/idad/" + idAd);
+               
+               getActivity().getContentResolver().delete(uriDelete, null, null);
+               mAdapter.remove(entry);
+               mAdapter.notifyDataSetChanged();
+           }
+               
+               /**
+                * When setHasOptionsMenu(true) we populate the action bar with this menu. It is merged to the
+                * current action bar menu (if there is no)
+                */
+               @Override 
+               public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+                       // Place an action bar item for searching.
+                       MenuItem item = menu.add("Search");
+                       item.setIcon(android.R.drawable.ic_menu_search);
+                       item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                       SearchView sv = new SearchView(getActivity());
+                       sv.setOnQueryTextListener(this);
+                       item.setActionView(sv);
+                       
+                       item = menu.add("Login");
+                       item.setIcon(android.R.drawable.ic_lock_power_off);
+                       item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                       
+                       item = menu.add("Login");
+                       item.setIcon(android.R.drawable.ic_lock_power_off);
+                       item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                       
+                       item = menu.add("Login");
+                       item.setIcon(android.R.drawable.ic_lock_power_off);
+                       item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                       
+                       item = menu.add("Login");
+                       item.setIcon(android.R.drawable.ic_lock_power_off);
+                       item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                       
+                       item = menu.add("Login");
+                       item.setIcon(android.R.drawable.ic_lock_power_off);
+                       item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                       
+                       item = menu.add("Login");
+                       item.setIcon(android.R.drawable.ic_menu_upload);
+                       item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                       
+                       item = menu.add("Login");
+                       item.setIcon(android.R.drawable.ic_lock_power_off);
+                       item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+
+               }
+
+               @Override
+               public Loader<List<AdsEntry>> onCreateLoader(int id, Bundle args) {
+                       // This is called when a new Loader needs to be created.  This
+                       // sample only has one Loader with no arguments, so it is simple.
+                       return new AdsListLoader(getActivity());
+               }
+
+               @Override
+               public void onLoadFinished(Loader<List<AdsEntry>> loader, List<AdsEntry> data) {
+                       mAdapter.setData(data);
+
+                       // The list should now be shown.
+                       if (isResumed()) {
+                               setListShown(true);
+                       } else {
+                               setListShownNoAnimation(true);
+                       }
+
+                       if (isMyServiceRunning()) {
+                               showNotification(0, 0, getText(R.string.remote_service_content_empty_notification));
+                       }
+               }
+
+               @Override
+               public void onLoaderReset(Loader<List<AdsEntry>> loader) {
+                       mAdapter.setData(null);
+               }
+
+               @Override
+               public boolean onQueryTextSubmit(String query) {
+                       // Don't care about this.
+                       return true;
+               }
+
+               @Override
+               public boolean onQueryTextChange(String newText) {
+                       // Called when the action bar search text has changed.  Update
+                       // the search filter, and restart the loader to do a new query
+                       // with this filter.
+                       mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+                       getLoaderManager().restartLoader(0, null, this);
+                       return true;
+               }
+               
+               private boolean isMyServiceRunning() {
+                       ActivityManager manager = (ActivityManager) getActivity().getSystemService(Context.ACTIVITY_SERVICE);
+                       for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+                               if (MobiAdsService.class.getName().equals(service.service.getClassName())) {
+                                       return true;
+                               }
+                       }
+                       return false;
+               }
+               
+               public void showNotification(final int level, final int noReadAds, CharSequence contentText) {        
+                       NotificationManager notificationManager = (NotificationManager)getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
+               
+                       // Set the icon, scrolling text and timestamp
+                       Notification.Builder notificationBuilder = new Notification.Builder(getActivity().getApplicationContext()).
+                                                                                                               setSmallIcon(R.drawable.wheelnotification, level).
+                                                                                                                       setTicker(getText(R.string.remote_service_started_notification)).
+                                                                                                                               setWhen(System.currentTimeMillis()).
+                                                                                                                                       setContentText(contentText).
+                                                                                                                                               setContentTitle(getText(R.string.remote_service_title_notification)).
+                                                                                                                                                       setNumber(noReadAds);
+                       Notification notification = notificationBuilder.getNotification();
+                       notification.flags |= Notification.FLAG_NO_CLEAR;
+
+                       // Send the notification.
+                       // We use a string id because it is a unique number.  We use it later to cancel.
+                       notificationManager.notify(R.string.remote_service_title_notification, notification);
+               }
+       }
+       
+       
+       
+
+       public static class AdsEntryAdapter extends ArrayAdapter<AdsEntry> {
+               private final int adsItemLayoutResource;
+
+               public AdsEntryAdapter(final Context context, final int adsItemLayoutResource) {
+                       super(context, 0);
+                       this.adsItemLayoutResource = adsItemLayoutResource;
+               }
+
+               public void setData(List<AdsEntry> data) {
+                       clear();
+                       if (data != null) {
+                               addAll(data);
+                       }
+               }
+
+               /**
+                * Populate new items in the list.
+                */
+               @Override
+               public View getView(final int position, final View convertView, final ViewGroup parent) {
+       
+                       // We need to get the best view (re-used if possible) and then
+                       // retrieve its corresponding ViewHolder, which optimizes lookup efficiency
+                       final View view = getWorkingView(convertView);
+                       final ViewHolder viewHolder = getViewHolder(view);
+                       final AdsEntry entry = getItem(position);
+       
+                       // Setting the text view
+                       viewHolder.titleView.setText(entry.getTitle());
+       
+                       viewHolder.textView.setText(entry.getText());
+       
+                       // Setting image view
+                       viewHolder.imageView.setImageBitmap(entry.getIcon());
+                       
+       
+                       return view;
+               }
+
+               private View getWorkingView(final View convertView) {
+                       // The workingView is basically just the convertView re-used if possible
+                       // or inflated new if not possible
+                       View workingView = null;
+       
+                       if(null == convertView) {
+                               final Context context = getContext();
+                               final LayoutInflater inflater = (LayoutInflater)context.getSystemService
+                                               (Context.LAYOUT_INFLATER_SERVICE);
+               
+                               workingView = inflater.inflate(adsItemLayoutResource, null);
+                       } else {
+                               workingView = convertView;
+                       }
+       
+                       return workingView;
+               }
+
+               private ViewHolder getViewHolder(final View workingView) {
+                       // The viewHolder allows us to avoid re-looking up view references
+                       // Since views are recycled, these references will never change
+                       final Object tag = workingView.getTag();
+                       ViewHolder viewHolder = null;
+       
+       
+                       if(null == tag || !(tag instanceof ViewHolder)) {
+                               viewHolder = new ViewHolder();
+               
+                               viewHolder.titleView = (TextView) workingView.findViewById(R.id.ads_entry_title);
+                               viewHolder.textView = (TextView) workingView.findViewById(R.id.ads_entry_text);
+                               viewHolder.imageView = (ImageView) workingView.findViewById(R.id.ads_entry_icon);
+               
+                               workingView.setTag(viewHolder);
+               
+                       } else {
+                               viewHolder = (ViewHolder) tag;
+                       }
+       
+                       return viewHolder;
+               }
+
+               /**
+                * ViewHolder allows us to avoid re-looking up view references
+                * Since views are recycled, these references will never change
+                */
+               private static class ViewHolder {
+                       public TextView titleView;
+                       public TextView textView;
+                       public ImageView imageView;
+               }
+       }
+
+       /**
+        * Encapsulates information about an ads entry
+        */
+       public static class AdsEntry {
+
+               private final String title;
+               private final String text;
+               private final Bitmap icon;
+               private final int idAd;
+               private final String URL;
+
+               public AdsEntry(final String title, final String text, final Bitmap icon, final int idAd, final String URL) {
+                       this.title = title;
+                       this.text = text;
+                       this.icon = icon;
+                       this.idAd = idAd;
+                       this.URL = URL;
+               }
+
+               /**
+                * @return Title of ads entry
+                */
+               public String getTitle() {
+                       return title;
+               }
+
+               /**
+                * @return Text of ads entry
+                */
+               public String getText() {
+                       return text;
+               }
+
+               /**
+                * @return Icon of this ads entry
+                */
+               public Bitmap getIcon() {
+                       return icon;
+               }
+
+               /**
+                * @return Ad unique identifier of this ads entry
+                */
+               public int getIdAd() {
+                       return idAd;
+               }
+
+               /**
+                * @return URL matching this ad.
+                */
+               public String getURL() {
+                       return URL;
+               }
+       }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsNewAdsActivity.java b/Android/MobiAdsReloaded/src/de/android/mobiads/list/MobiAdsNewAdsActivity.java
new file mode 100644 (file)
index 0000000..7fe60f2
--- /dev/null
@@ -0,0 +1,144 @@
+package de.android.mobiads.list;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import de.android.mobiads.MobiAdsService;
+import de.android.mobiads.R;
+import de.android.mobiads.provider.Indexer;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.AdapterView.OnItemClickListener;
+
+public class MobiAdsNewAdsActivity extends Activity {
+       private static final String TAG = "MobiAdsNewAdsActivity";
+       
+        @Override
+           public void onResume() {
+               super.onResume();
+               setContentView(R.layout.latest_ads);
+               
+               // Setup the list view
+               final ListView newsEntryListView = (ListView) findViewById(R.id.list_frag);
+               final AdsEntryAdapter newsEntryAdapter = new AdsEntryAdapter(this, R.layout.ads_entry_list_item);
+               newsEntryListView.setAdapter(newsEntryAdapter);
+               
+               // Populate the list, through the adapter. Should I populate the whole list right now? I do not think so...
+               // Find out a way to populate this list just when it is required... :/
+               for(final AdsEntry entry : getAdsEntries()) {
+                       newsEntryAdapter.add(entry);
+               }
+               
+               newsEntryListView.setOnItemClickListener(new OnItemClickListener() {
+
+                               @Override
+                               public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                                       Uri uri = Uri.parse(newsEntryAdapter.getItem(position).getTitle());
+                                       startActivity(new Intent(Intent.ACTION_VIEW, uri));
+                               }
+               });
+               
+           }
+           
+           private List<AdsEntry> getAdsEntries() {
+               final List<AdsEntry> entries = new ArrayList<AdsEntry>();
+               final Uri uri = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer" + "/isRead/");
+               final ContentValues values = new ContentValues();
+
+               Cursor cursor = this.getContentResolver().query(uri, null, null, null, null);
+               try {
+                               if (cursor.moveToFirst()) {
+                                       do {
+                                               values.clear();
+                                               Bitmap bitMap = null;
+                                               FileInputStream file = null;
+                                               
+                                               try {
+                                                       file = this.openFileInput(cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_PATH)));
+                                                       bitMap = BitmapFactory.decodeStream(file);
+                                               } catch (FileNotFoundException e) {
+                                                       //Giving more chances to other ads
+                                                       continue;
+                                               } catch (IllegalArgumentException e) {
+                                                       //Giving more chances to other ads
+                                                       continue;
+                                               }
+                                               finally {
+                                                       if (file != null) {
+                                                               try {
+                                                                       file.close();
+                                                               } catch (IOException e) {
+                                                                       Log.w(TAG, "Error while closing image file.");
+                                                               }
+                                                       }
+                                               }
+                                               entries.add(new AdsEntry(cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_AD_NAME)), 
+                                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_TEXT)), bitMap,
+                                                               cursor.getInt(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_ID_AD)),
+                                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_URL))));
+
+                                               
+                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index.COLUMN_NAME_PATH));
+                                               values.put(Indexer.Index.COLUMN_NAME_IS_READ, new Integer(1));
+                                               Uri uriUpdate = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer/" + 
+                                                                                                               cursor.getString(cursor.getColumnIndexOrThrow(Indexer.Index._ID)));
+                                               getContentResolver().update(uriUpdate, values, null, null);
+                                       }while (cursor.moveToNext());
+                               } 
+               }finally {
+                                       cursor.close();
+                       }
+               
+               if (this.isMyServiceRunning()) {
+                       showNotification(0, 0, getText(R.string.remote_service_content_empty_notification));
+               }
+               
+               return entries;
+           }
+           
+           private boolean isMyServiceRunning() {
+               ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+               for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+                   if (MobiAdsService.class.getName().equals(service.service.getClassName())) {
+                       return true;
+                   }
+               }
+               return false;
+           }
+           
+           public void showNotification(final int level, final int noReadAds, CharSequence contentText) {        
+               NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+                       
+               // Set the icon, scrolling text and timestamp
+               Notification.Builder notificationBuilder = new Notification.Builder(getApplicationContext()).
+                                                                                                       setSmallIcon(R.drawable.wheelnotification, level).
+                                                                                                               setTicker(getText(R.string.remote_service_started_notification)).
+                                                                                                                       setWhen(System.currentTimeMillis()).
+                                                                                                                               setContentText(contentText).
+                                                                                                                                       setContentTitle(getText(R.string.remote_service_title_notification)).
+                                                                                                                                               setNumber(noReadAds);
+               Notification notification = notificationBuilder.getNotification();
+               notification.flags |= Notification.FLAG_NO_CLEAR;
+
+               // Send the notification.
+               // We use a string id because it is a unique number.  We use it later to cancel.
+               notificationManager.notify(R.string.remote_service_title_notification, notification);
+           }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/provider/Indexer.java b/Android/MobiAdsReloaded/src/de/android/mobiads/provider/Indexer.java
new file mode 100644 (file)
index 0000000..c44756c
--- /dev/null
@@ -0,0 +1,87 @@
+package de.android.mobiads.provider;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+public final class Indexer {
+       public static final String AUTHORITY = "de.android.mobiads.provider.IndexerProvider";
+
+       // This class cannot be instantiated
+    private Indexer() {
+    }
+    
+    /**
+     * Indexer table contract
+     */
+    public static final class Index implements BaseColumns {
+       
+       // This class cannot be instantiated
+        private Index() {}
+        
+        /**
+         * The table name offered by this provider
+         */
+        public static final String TABLE_NAME = "indexer";
+        
+        /**
+         * Column name for the path of the file
+         * <P>Type: TEXT</P>
+         */
+        public static final String COLUMN_NAME_PATH = "path";
+        
+        /**
+         * Column name for the ad unique identifier number
+         * <P>Type: INTEGER</P>
+         */
+        public static final String COLUMN_NAME_ID_AD = "idad";
+        
+        /**
+         * Column name for the ad's text.
+         * <P>Type: TEXT</P>
+         */
+        public static final String COLUMN_NAME_TEXT = "text";
+        
+        /**
+         * Column name for the ad's URL.
+         * <P>Type: TEXT</P>
+         */
+        public static final String COLUMN_NAME_URL = "url";
+        
+        /**
+         * Column name for the Sqlite3 integer as boolean field. <br>
+         * It let us know if the ad was already read by the user or not.
+         * <P>Type: TEXT</P>
+         */
+        public static final String COLUMN_NAME_IS_READ = "isRead";
+        
+        /**
+         * Column name for the Sqlite3 integer as boolean field. <br>
+         * It let us know if the ad was already read by the user or not.
+         * <P>Type: TEXT</P>
+         */
+        public static final String COLUMN_NAME_AD_NAME = "adName";
+        
+        /**
+         * The default sort order for this table
+         */
+        public static final String DEFAULT_SORT_ORDER = Index._ID;
+        
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of notes.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.index";
+        
+        /**
+         * The MIME type of a {@link #CONTENT_URI} sub-directory of a single
+         * note.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.google.index";
+        
+        /**
+         * The content URI base for a single index. Callers must
+         * append a numeric note id to this Uri to retrieve an index
+         */
+        public static final Uri CONTENT_ID_URI_BASE
+            = Uri.parse("content://de.android.mobiads.provider/indexer/");
+    }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/provider/IndexerOpenHelper.java b/Android/MobiAdsReloaded/src/de/android/mobiads/provider/IndexerOpenHelper.java
new file mode 100644 (file)
index 0000000..bb88fc3
--- /dev/null
@@ -0,0 +1,64 @@
+package de.android.mobiads.provider;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+public class IndexerOpenHelper extends SQLiteOpenHelper {
+       // Used for debugging and logging
+    private static final String TAG = "IndexerOpenHelper";
+       
+       private static final String DATABASE_NAME = "mobiads.db";
+       private static final int DATABASE_VERSION = 1;
+
+
+    IndexerOpenHelper(Context context) {
+        super(context, DATABASE_NAME, null, DATABASE_VERSION);
+    }
+
+
+    /**
+     *
+     * Creates the underlying database with table name and column names taken from the
+     * Indexer class.
+     */
+    @Override
+       public void onCreate(SQLiteDatabase db) {
+               db.execSQL("CREATE TABLE " + Indexer.Index.TABLE_NAME + " ("
+                               + Indexer.Index._ID + " INTEGER PRIMARY KEY, "
+                               + Indexer.Index.COLUMN_NAME_ID_AD + " INTEGER" + " UNIQUE" + " NOT NULL, "
+                               + Indexer.Index.COLUMN_NAME_PATH + " TEXT(15)" + " UNIQUE" + " NOT NULL, "
+                               + Indexer.Index.COLUMN_NAME_URL + " TEXT(3000)" + " NOT NULL, "
+                               + Indexer.Index.COLUMN_NAME_TEXT + " TEXT(500)" + " NOT NULL, "
+                               + Indexer.Index.COLUMN_NAME_IS_READ + " INTEGER" + " NOT NULL, "
+                               + Indexer.Index.COLUMN_NAME_AD_NAME + " TEXT(255)" + " NOT NULL "
+                               + ");");
+       }
+
+    @Override
+    public void close() {
+       super.close();
+       Log.i("IndexerOpenHelper", "close");
+    }
+
+    /**
+     *
+     * Demonstrates that the provider must consider what happens when the
+     * underlying datastore is changed. In this sample, the database is upgraded the database
+     * by destroying the existing data.
+     * A real application should upgrade the database in place.
+     */
+       @Override
+       public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+               // Logs that the database is being upgraded
+        Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+                + newVersion + ", which will destroy all old data");
+
+        // Kills the table and existing data
+        db.execSQL("DROP TABLE IF EXISTS " + Indexer.Index.TABLE_NAME);
+
+        // Recreates the database with a new version
+        onCreate(db);
+       }
+}
diff --git a/Android/MobiAdsReloaded/src/de/android/mobiads/provider/IndexerProvider.java b/Android/MobiAdsReloaded/src/de/android/mobiads/provider/IndexerProvider.java
new file mode 100644 (file)
index 0000000..e43d45c
--- /dev/null
@@ -0,0 +1,478 @@
+package de.android.mobiads.provider;
+
+import java.util.HashMap;
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteReadOnlyDatabaseException;
+import android.net.Uri;
+import android.text.TextUtils;
+
+
+/**
+ *
+ */
+public class IndexerProvider extends ContentProvider {
+    // Creates a UriMatcher object.
+    private static final UriMatcher sUriMatcher;
+    
+    /**
+     * A projection map used to select columns from the database
+     */
+    private static HashMap<String, String> sIndexerProjectionMap;
+    
+    // Handle to a new DatabaseHelper.
+    private IndexerOpenHelper mOpenHelper;
+    
+    // The incoming URI matches the Notes URI pattern
+    private static final int INDEXER = 1;
+    
+    // The incoming URI matches the Note ID URI pattern
+    private static final int INDEXER_ID = 2;
+    
+    private static final int INDEXER_IDAD = 3;
+    
+    private static final int NOREAD = 4;
+    
+    static {
+       
+       /*
+         * Creates and initializes the URI matcher
+         */
+        // Create a new instance
+        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+        
+        // Add a pattern that routes URIs terminated with "indexer" to a INDEXER operation
+        sUriMatcher.addURI("de.android.mobiads.provider", Indexer.Index.TABLE_NAME, INDEXER);
+        
+        // Add a pattern that routes URIs terminated with "indexer" plus an integer
+        // to a index ID operation
+        sUriMatcher.addURI("de.android.mobiads.provider", Indexer.Index.TABLE_NAME + "/#", INDEXER_ID);
+        
+        sUriMatcher.addURI("de.android.mobiads.provider", Indexer.Index.TABLE_NAME + "/" + Indexer.Index.COLUMN_NAME_ID_AD + "/#", INDEXER_IDAD);
+        
+        sUriMatcher.addURI("de.android.mobiads.provider", Indexer.Index.TABLE_NAME + "/" + Indexer.Index.COLUMN_NAME_IS_READ, NOREAD);
+        
+        /*
+         * Creates and initializes a projection map that returns all columns
+         */
+
+        // Creates a new projection map instance. The map returns a column name
+        // given a string. The two are usually equal.
+        sIndexerProjectionMap = new HashMap<String, String>();
+
+        // Maps the string "_ID" to the column name "_ID"
+        sIndexerProjectionMap.put(Indexer.Index._ID, Indexer.Index._ID);
+
+        // Maps "idad" to "idad"
+        sIndexerProjectionMap.put(Indexer.Index.COLUMN_NAME_ID_AD, Indexer.Index.COLUMN_NAME_ID_AD);
+
+        // Maps "path" to "path"
+        sIndexerProjectionMap.put(Indexer.Index.COLUMN_NAME_PATH, Indexer.Index.COLUMN_NAME_PATH);
+        
+        sIndexerProjectionMap.put(Indexer.Index.COLUMN_NAME_TEXT, Indexer.Index.COLUMN_NAME_TEXT);
+        
+        sIndexerProjectionMap.put(Indexer.Index.COLUMN_NAME_URL, Indexer.Index.COLUMN_NAME_URL);
+        
+        sIndexerProjectionMap.put(Indexer.Index.COLUMN_NAME_IS_READ, Indexer.Index.COLUMN_NAME_IS_READ);
+        
+        sIndexerProjectionMap.put(Indexer.Index.COLUMN_NAME_AD_NAME, Indexer.Index.COLUMN_NAME_AD_NAME);
+    }
+    
+    
+    /**
+        *
+        * Initializes the provider by creating a new DatabaseHelper. onCreate() is called
+        * automatically when Android creates the provider in response to a resolver request from a
+        * client.
+        */
+       @Override
+       public boolean onCreate() {
+      // Creates a new helper object. Note that the database itself isn't opened until
+      // something tries to access it, and it's only created if it doesn't already exist.
+      mOpenHelper = new IndexerOpenHelper(getContext());
+
+      // Assumes that any failures will be reported by a thrown exception.
+      return true;
+       }
+    
+       
+    /**
+     * This is called when a client calls
+     * {@link android.content.ContentResolver#delete(Uri, String, String[])}.
+     * Deletes records from the database. If the incoming URI matches the note ID URI pattern,
+     * this method deletes the one record specified by the ID in the URI. Otherwise, it deletes a
+     * a set of records. The record or records must also match the input selection criteria
+     * specified by where and whereArgs.
+     *
+     * If rows were deleted, then listeners are notified of the change.
+     * @return If a "where" clause is used, the number of rows affected is returned, otherwise
+     * 0 is returned. To delete all rows and get a row count, use "1" as the where clause.
+     * @throws IllegalArgumentException if the incoming URI pattern is invalid.
+     */
+    @Override
+       public int delete(Uri uri, String selection, String[] selectionArgs) {
+       // Opens the database object in "write" mode.
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+       
+        String finalWhere;
+
+        int count;
+
+        // Does the delete based on the incoming URI pattern.
+        switch (sUriMatcher.match(uri)) {
+               case INDEXER:
+                       // If the incoming pattern matches the general pattern for notes, does a delete
+                       // based on the incoming "where" columns and arguments.
+                count = db.delete(
+                       Indexer.Index.TABLE_NAME,  // The database table name
+                       selection,                 // The incoming where clause column names
+                       selectionArgs              // The incoming where clause values
+                );
+                break;
+               case INDEXER_ID:
+                       /*
+                        * Starts a final WHERE clause by restricting it to the
+                        * desired note ID.
+                        */
+                       finalWhere =
+                    Indexer.Index._ID +                // The ID column name
+                    " = " +                                    // test for equality
+                    uri.getPathSegments().get(1);      // the incoming note ID
+                        
+
+                       // If there were additional selection criteria, append them to the final
+                       // WHERE clause
+                       if (selection != null) {
+                               finalWhere = finalWhere + " AND " + selection;
+                       }
+
+                       // Performs the delete.
+                       count = db.delete(
+                                       Indexer.Index.TABLE_NAME,  // The database table name.
+                                       finalWhere,                // The final WHERE clause
+                                       selectionArgs                  // The incoming where clause values.
+                       );
+                       break;
+                       
+               case INDEXER_IDAD:
+                       finalWhere = Indexer.Index.COLUMN_NAME_ID_AD + // the name of the ID column
+                          "=" +
+                          // the position of the Advertisement ID itself in the incoming URI
+                          uri.getPathSegments().get(2);
+                       if (selection != null) {
+                               finalWhere = finalWhere + " AND " + selection;
+                       }
+                       count = db.delete(
+                                       Indexer.Index.TABLE_NAME,  // The database table name.
+                                       finalWhere,                // The final WHERE clause
+                                       selectionArgs                  // The incoming where clause values.
+                       );
+                       break;
+               default:
+                       throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+       
+               return count;
+       }
+
+    /**
+     * This is called when a client calls {@link android.content.ContentResolver#getType(Uri)}.
+     * Returns the MIME data type of the URI given as a parameter.
+     *
+     * @param uri The URI whose MIME type is desired.
+     * @return The MIME type of the URI.
+     * @throws IllegalArgumentException if the incoming URI pattern is invalid.
+     */
+       @Override
+       public String getType(Uri uri) {
+               /**
+                * Chooses the MIME type based on the incoming URI pattern
+            */
+               switch (sUriMatcher.match(uri)) {
+                       // If the pattern is for notes or live folders, returns the general content type.
+               case INDEXER:
+                       return Indexer.Index.CONTENT_TYPE;
+                       
+               // If the pattern is for note IDs, returns the note ID content type.
+               case INDEXER_ID:
+                       return Indexer.Index.CONTENT_ITEM_TYPE;
+
+               // If the URI pattern doesn't match any permitted patterns, throws an exception.
+               default:
+                       throw new IllegalArgumentException("Unknown URI " + uri);
+               }
+       }
+
+       
+    /**
+     * This is called when a client calls
+     * {@link android.content.ContentResolver#insert(Uri, ContentValues)}.
+     * Inserts a new row into the database. This method sets up default values for any
+     * columns that are not included in the incoming map.
+     * If rows were inserted, then listeners are notified of the change.
+     * @return The row ID of the inserted row.
+     * @throws SQLException if the insertion fails.
+     */
+       @Override
+       public Uri insert(Uri uri, ContentValues initialValues) {
+               // A map to hold the new record's values.
+        ContentValues values;
+                               
+               // If the incoming values map is not null, uses it for the new values.
+        if (initialValues != null) {
+            values = new ContentValues(initialValues);
+
+        } else {
+            // Otherwise, create a new value map
+            values = new ContentValues();
+        }
+               
+        // If the values map doesn't contain the path or ad identifier number.
+        if ((values.containsKey(Indexer.Index.COLUMN_NAME_PATH) == false) || 
+               (values.containsKey(Indexer.Index.COLUMN_NAME_ID_AD) == false) || 
+               (values.containsKey(Indexer.Index.COLUMN_NAME_TEXT) == false) || 
+               (values.containsKey(Indexer.Index.COLUMN_NAME_URL) == false) ||
+               (values.containsKey(Indexer.Index.COLUMN_NAME_AD_NAME) == false) ||
+               (values.containsKey(Indexer.Index.COLUMN_NAME_IS_READ) == false)){
+                       throw new SQLException("Missed parameter. Failed to insert row into " + uri);
+        }      
+        
+        // Opens the database object in "write" mode.
+        // This will trigger its creation if it doesn't already exist.
+               SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+               
+               // Performs the insert and returns the ID of the new index.
+               //values.put(Indexer.Index.COLUMN_NAME_TEXT, "Texto de prueba");
+               //values.put(Indexer.Index.COLUMN_NAME_URL, "http://gumartinm.name");
+        long rowId = db.insert(
+               Indexer.Index.TABLE_NAME, // The table to insert into.
+            null,                                        // A hack, SQLite sets this column value to null if values is empty.
+            values                    // A map of column names, and the values to insert into the columns.
+        );
+               
+        // If the insert succeeded, the row ID exists.
+        if (rowId > 0) {
+            // Creates a URI with the index ID pattern and the new row ID appended to it.
+            Uri noteUri = ContentUris.withAppendedId(Indexer.Index.CONTENT_ID_URI_BASE, rowId);
+
+            // Notifies observers registered against this provider that the data changed.
+            getContext().getContentResolver().notifyChange(noteUri, null);
+            return noteUri;
+        }
+
+        // If the insert didn't succeed, then the rowID is <= 0. Throws an exception.
+        throw new SQLException("Failed to insert row into " + uri);
+       }
+       
+       
+       /**
+        * This method is called when a client calls
+        * {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)}.
+        * Queries the database and returns a cursor containing the results.
+        *
+        * @return A cursor containing the results of the query. The cursor exists but is empty if
+        * the query returns no results or an exception occurs.
+        * @throws IllegalArgumentException if the incoming URI pattern is invalid.
+        */
+       @Override
+       public Cursor query(Uri uri, String[] projection, String selection,
+                       String[] selectionArgs, String sortOrder) {
+               // Constructs a new query builder and sets its table name
+           SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+           qb.setTables(Indexer.Index.TABLE_NAME);
+               
+           /**
+            * Choose the projection and adjust the "where" clause based on URI pattern-matching.
+            */
+           switch (sUriMatcher.match(uri)) {
+               // If the incoming URI is for notes, chooses the Indexer projection
+               case INDEXER:
+                       qb.setProjectionMap(sIndexerProjectionMap);
+                      
+                       break;
+
+               /* If the incoming URI is for a single note identified by its ID, chooses the
+                * note ID projection, and appends "_ID = <noteID>" to the where clause, so that
+                * it selects that single note
+                */
+               case INDEXER_ID:
+                       qb.setProjectionMap(sIndexerProjectionMap);
+                       qb.appendWhere(
+                       Indexer.Index._ID +             // the name of the ID column
+                          "=" +
+                          // the position of the note ID itself in the incoming URI
+                          uri.getPathSegments().get(1));
+                   break;
+                   
+               case INDEXER_IDAD:
+                       qb.setProjectionMap(sIndexerProjectionMap);
+                       qb.appendWhere(
+                       Indexer.Index.COLUMN_NAME_ID_AD + // the name of the ID column
+                          "=" +
+                          // the position of the Advertisement ID itself in the incoming URI
+                          uri.getPathSegments().get(2));
+                       break;
+               case NOREAD:
+                       qb.setProjectionMap(sIndexerProjectionMap);
+                       qb.appendWhere(Indexer.Index.COLUMN_NAME_IS_READ + " = " + "0");
+                       break;
+               default:
+                   // If the URI doesn't match any of the known patterns, throw an exception.
+                   throw new IllegalArgumentException("Unknown URI " + uri);
+           }
+           
+           String orderBy;
+           // If no sort order is specified, uses the default
+           if (TextUtils.isEmpty(sortOrder)) {
+               orderBy = Indexer.Index.DEFAULT_SORT_ORDER;
+           } else {
+               // otherwise, uses the incoming sort order
+               orderBy = sortOrder;
+           }
+
+           // Opens the database object in "read" mode, since no writes need to be done.
+           SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+
+           /*
+            * Performs the query. If no problems occur trying to read the database, then a Cursor
+            * object is returned; otherwise, the cursor variable contains null. If no records were
+            * selected, then the Cursor object is empty, and Cursor.getCount() returns 0.
+            */
+           Cursor c = qb.query(
+                       db,            // The database to query
+                       projection,    // The columns to return from the query
+                       selection,     // The columns for the where clause
+                       selectionArgs, // The values for the where clause
+                       null,          // don't group the rows
+                       null,          // don't filter by row groups
+                       orderBy        // The sort order
+           );
+           
+           if (c == null) {
+               // If the cursor is null, throw an exception
+            throw new SQLiteReadOnlyDatabaseException("Unable to query " + uri);
+           }
+           // Tells the Cursor what URI to watch, so it knows when its source data changes
+           c.setNotificationUri(getContext().getContentResolver(), uri);
+           return c;
+       }
+
+    /**
+     * This is called when a client calls
+     * {@link android.content.ContentResolver#update(Uri,ContentValues,String,String[])}
+     * Updates records in the database. The column names specified by the keys in the values map
+     * are updated with new data specified by the values in the map. If the incoming URI matches the
+     * note ID URI pattern, then the method updates the one record specified by the ID in the URI;
+     * otherwise, it updates a set of records. The record or records must match the input
+     * selection criteria specified by where and whereArgs.
+     * If rows were updated, then listeners are notified of the change.
+     *
+     * @param uri The URI pattern to match and update.
+     * @param values A map of column names (keys) and new values (values).
+     * @param where An SQL "WHERE" clause that selects records based on their column values. If this
+     * is null, then all records that match the URI pattern are selected.
+     * @param whereArgs An array of selection criteria. If the "where" param contains value
+     * placeholders ("?"), then each placeholder is replaced by the corresponding element in the
+     * array.
+     * @return The number of rows updated.
+     * @throws IllegalArgumentException if the incoming URI pattern is invalid.
+     */
+       @Override
+       public int update(Uri uri, ContentValues values, String where,
+                       String[] whereArgs) {
+               // Opens the database object in "write" mode.
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int count;
+        String finalWhere;
+
+        // Does the update based on the incoming URI pattern
+        switch (sUriMatcher.match(uri)) {
+        
+               // If the incoming URI matches the general notes pattern, does the update based on
+               // the incoming data.
+               case INDEXER:
+                       
+                       // Does the update and returns the number of rows updated.
+                count = db.update(
+                    Indexer.Index.TABLE_NAME, // The database table name.
+                    values,                   // A map of column names and new values to use.
+                    where,                    // The where clause column names.
+                    whereArgs                 // The where clause column values to select on.
+                );
+                break;
+            
+            // If the incoming URI matches a single note ID, does the update based on the incoming
+            // data, but modifies the where clause to restrict it to the particular note ID.
+               case INDEXER_ID:
+                       
+                       // From the incoming URI, get the note ID
+                String noteId = uri.getPathSegments().get(1);
+
+                /*
+                 * Starts creating the final WHERE clause by restricting it to the incoming
+                 * note ID.
+                 */
+                finalWhere =
+                                       Indexer.Index._ID +         // The ID column name
+                                       " = " +                         // test for equality
+                                       noteId;                     // the incoming note ID
+
+                // If there were additional selection criteria, append them to the final WHERE
+                // clause
+                if (where !=null) {
+                    finalWhere = finalWhere + " AND " + where;
+                }
+
+
+                // Does the update and returns the number of rows updated.
+                count = db.update(
+                       Indexer.Index.TABLE_NAME, // The database table name.
+                    values,                   // A map of column names and new values to use.
+                    finalWhere,               // The final WHERE clause to use
+                                              // placeholders for whereArgs
+                    whereArgs                 // The where clause column values to select on, or
+                                              // null if the values are in the where argument.
+                );
+                break;
+                
+               case NOREAD:
+                       finalWhere = Indexer.Index.COLUMN_NAME_IS_READ + " = " + "0";
+                       if (where !=null) {
+                    finalWhere = finalWhere + " AND " + where;
+                }
+                       // Does the update and returns the number of rows updated.
+                count = db.update(
+                       Indexer.Index.TABLE_NAME, // The database table name.
+                    values,                   // A map of column names and new values to use.
+                    finalWhere,               // The final WHERE clause to use
+                                              // placeholders for whereArgs
+                    whereArgs                 // The where clause column values to select on, or
+                                              // null if the values are in the where argument.
+                );
+                       break;
+               default:
+                       throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+               
+        /*Gets a handle to the content resolver object for the current context, and notifies it
+         * that the incoming URI changed. The object passes this along to the resolver framework,
+         * and observers that have registered themselves for the provider are notified.
+         */
+        getContext().getContentResolver().notifyChange(uri, null);
+
+        // Returns the number of rows updated.
+        return count;
+       }
+       
+       @Override
+       public void shutdown() {
+               mOpenHelper.close();
+       }
+}
\ No newline at end of file