New MobiAds Android implementation: with tabs.
authorgumartinm <gustavo@gumartinm.name>
Fri, 15 Jun 2012 00:47:01 +0000 (02:47 +0200)
committergumartinm <gustavo@gumartinm.name>
Fri, 15 Jun 2012 00:47:01 +0000 (02:47 +0200)
32 files changed:
Android/MobiAdsTabs/AndroidManifest.xml [new file with mode: 0644]
Android/MobiAdsTabs/gen/de/android/mobiads/BuildConfig.java [new file with mode: 0644]
Android/MobiAdsTabs/gen/de/android/mobiads/R.java [new file with mode: 0644]
Android/MobiAdsTabs/proguard-project.txt [new file with mode: 0644]
Android/MobiAdsTabs/project.properties [new file with mode: 0644]
Android/MobiAdsTabs/res/drawable-hdpi/ic_launcher.png [new file with mode: 0644]
Android/MobiAdsTabs/res/drawable-ldpi/ic_launcher.png [new file with mode: 0644]
Android/MobiAdsTabs/res/drawable-mdpi/ic_launcher.png [new file with mode: 0644]
Android/MobiAdsTabs/res/drawable-xhdpi/ic_launcher.png [new file with mode: 0644]
Android/MobiAdsTabs/res/drawable/wheelnotification.xml [new file with mode: 0644]
Android/MobiAdsTabs/res/layout/login.xml [new file with mode: 0644]
Android/MobiAdsTabs/res/layout/main.xml [new file with mode: 0644]
Android/MobiAdsTabs/res/layout/mobiadslist.xml [new file with mode: 0644]
Android/MobiAdsTabs/res/layout/mobiadsnewadslist.xml [new file with mode: 0644]
Android/MobiAdsTabs/res/layout/news_entry_list_item.xml [new file with mode: 0644]
Android/MobiAdsTabs/res/menu/menuads.xml [new file with mode: 0644]
Android/MobiAdsTabs/res/values/strings.xml [new file with mode: 0644]
Android/MobiAdsTabs/res/xml/preferences.xml [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/Cookie.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/FragmentStack.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/MobiAdsLoginActivity.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/MobiAdsPreferences.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/MobiAdsService.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/MobiAdsTabsActivity.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/batch/MobiAdsBatch.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/list/AdsEntry.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/list/AdsEntryAdapter.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/list/MobiAdsListFragment.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/list/MobiAdsNewAdsActivity.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/provider/Indexer.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/provider/IndexerOpenHelper.java [new file with mode: 0644]
Android/MobiAdsTabs/src/de/android/mobiads/provider/IndexerProvider.java [new file with mode: 0644]

diff --git a/Android/MobiAdsTabs/AndroidManifest.xml b/Android/MobiAdsTabs/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..75b5985
--- /dev/null
@@ -0,0 +1,84 @@
+<?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" >
+
+    <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=".MobiAdsTabsActivity"
+            android:theme="@android:style/Theme.Holo"
+            android:screenOrientation="portrait"
+            android:configChanges="touchscreen|keyboard"
+            android:launchMode="standard" >
+            <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.MobiAdsNewAdsActivity"
+            android:theme="@android:style/Theme.Black" 
+            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/MobiAdsTabs/gen/de/android/mobiads/BuildConfig.java b/Android/MobiAdsTabs/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/MobiAdsTabs/gen/de/android/mobiads/R.java b/Android/MobiAdsTabs/gen/de/android/mobiads/R.java
new file mode 100644 (file)
index 0000000..02eb0bd
--- /dev/null
@@ -0,0 +1,75 @@
+/* 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=0x7f070006;
+        public static final int ads_entry_text=0x7f070008;
+        public static final int ads_entry_title=0x7f070007;
+        public static final int frameLayout1=0x7f070000;
+        public static final int frameLayout2=0x7f070003;
+        public static final int list=0x7f070005;
+        public static final int login_button=0x7f070004;
+        public static final int menuadsremove=0x7f070009;
+        public static final int password=0x7f070002;
+        public static final int username=0x7f070001;
+    }
+    public static final class layout {
+        public static final int login=0x7f030000;
+        public static final int main=0x7f030001;
+        public static final int mobiadslist=0x7f030002;
+        public static final int mobiadsnewadslist=0x7f030003;
+        public static final int news_entry_list_item=0x7f030004;
+    }
+    public static final class menu {
+        public static final int menuads=0x7f060000;
+    }
+    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_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/MobiAdsTabs/proguard-project.txt b/Android/MobiAdsTabs/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/MobiAdsTabs/project.properties b/Android/MobiAdsTabs/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/MobiAdsTabs/res/drawable-hdpi/ic_launcher.png b/Android/MobiAdsTabs/res/drawable-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..96a442e
Binary files /dev/null and b/Android/MobiAdsTabs/res/drawable-hdpi/ic_launcher.png differ
diff --git a/Android/MobiAdsTabs/res/drawable-ldpi/ic_launcher.png b/Android/MobiAdsTabs/res/drawable-ldpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..9923872
Binary files /dev/null and b/Android/MobiAdsTabs/res/drawable-ldpi/ic_launcher.png differ
diff --git a/Android/MobiAdsTabs/res/drawable-mdpi/ic_launcher.png b/Android/MobiAdsTabs/res/drawable-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..359047d
Binary files /dev/null and b/Android/MobiAdsTabs/res/drawable-mdpi/ic_launcher.png differ
diff --git a/Android/MobiAdsTabs/res/drawable-xhdpi/ic_launcher.png b/Android/MobiAdsTabs/res/drawable-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..71c6d76
Binary files /dev/null and b/Android/MobiAdsTabs/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/Android/MobiAdsTabs/res/drawable/wheelnotification.xml b/Android/MobiAdsTabs/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/MobiAdsTabs/res/layout/login.xml b/Android/MobiAdsTabs/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/MobiAdsTabs/res/layout/main.xml b/Android/MobiAdsTabs/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/MobiAdsTabs/res/layout/mobiadslist.xml b/Android/MobiAdsTabs/res/layout/mobiadslist.xml
new file mode 100644 (file)
index 0000000..2fb5935
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" />
+    
+    <ListView android:id="@+id/list"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/Android/MobiAdsTabs/res/layout/mobiadsnewadslist.xml b/Android/MobiAdsTabs/res/layout/mobiadsnewadslist.xml
new file mode 100644 (file)
index 0000000..d5a1517
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/new_ads" />
+    
+    <ListView android:id="@+id/list"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/Android/MobiAdsTabs/res/layout/news_entry_list_item.xml b/Android/MobiAdsTabs/res/layout/news_entry_list_item.xml
new file mode 100644 (file)
index 0000000..13a01c3
--- /dev/null
@@ -0,0 +1,40 @@
+<?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: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/MobiAdsTabs/res/menu/menuads.xml b/Android/MobiAdsTabs/res/menu/menuads.xml
new file mode 100644 (file)
index 0000000..d1b7991
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+      <item android:id="@+id/menuadsremove"
+          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" />
+</menu>
\ No newline at end of file
diff --git a/Android/MobiAdsTabs/res/values/strings.xml b/Android/MobiAdsTabs/res/values/strings.xml
new file mode 100644 (file)
index 0000000..d78b3e6
--- /dev/null
@@ -0,0 +1,34 @@
+<?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>
+</resources>
\ No newline at end of file
diff --git a/Android/MobiAdsTabs/res/xml/preferences.xml b/Android/MobiAdsTabs/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/MobiAdsTabs/src/de/android/mobiads/Cookie.java b/Android/MobiAdsTabs/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/MobiAdsTabs/src/de/android/mobiads/FragmentStack.java b/Android/MobiAdsTabs/src/de/android/mobiads/FragmentStack.java
new file mode 100644 (file)
index 0000000..9e77f27
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.android.mobiads;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+public class FragmentStack extends Activity {
+
+
+    public static class CountingFragment extends Fragment {
+        int mNum;
+
+        /**
+         * Create a new instance of CountingFragment, providing "num"
+         * as an argument.
+         */
+        static CountingFragment newInstance(int num) {
+            CountingFragment f = new CountingFragment();
+
+            // Supply num input as an argument.
+            Bundle args = new Bundle();
+            args.putInt("num", num);
+            f.setArguments(args);
+
+            return f;
+        }
+
+        /**
+         * When creating, retrieve this instance's number from its arguments.
+         */
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mNum = getArguments() != null ? getArguments().getInt("num") : 1;
+        }
+
+        /**
+         * The Fragment's UI is just a simple text view showing its
+         * instance number.
+         */
+//        @Override
+//        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+//                Bundle savedInstanceState) {
+//            View v = inflater.inflate(R.layout.logintab, container, false);
+//            View tv = v.findViewById(R.id.text);
+//            ((TextView)tv).setText("Fragment #" + mNum);
+//            tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
+//            return v;
+//        }
+    }
+
+}
diff --git a/Android/MobiAdsTabs/src/de/android/mobiads/MobiAdsLoginActivity.java b/Android/MobiAdsTabs/src/de/android/mobiads/MobiAdsLoginActivity.java
new file mode 100644 (file)
index 0000000..03e6e4d
--- /dev/null
@@ -0,0 +1,165 @@
+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);
+               
+               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/MobiAdsTabs/src/de/android/mobiads/MobiAdsPreferences.java b/Android/MobiAdsTabs/src/de/android/mobiads/MobiAdsPreferences.java
new file mode 100644 (file)
index 0000000..92ffd98
--- /dev/null
@@ -0,0 +1,50 @@
+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);
+        
+        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/MobiAdsTabs/src/de/android/mobiads/MobiAdsService.java b/Android/MobiAdsTabs/src/de/android/mobiads/MobiAdsService.java
new file mode 100644 (file)
index 0000000..6364c64
--- /dev/null
@@ -0,0 +1,185 @@
+package de.android.mobiads;
+
+import de.android.mobiads.batch.MobiAdsBatch;
+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, null);
+        }
+        else {
+               contentText = getText(R.string.remote_service_content_notification);
+               showNotification(0, noReadCount, contentText, MobiAdsNewAdsActivity.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, MobiAdsNewAdsActivity.class);
+            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/MobiAdsTabs/src/de/android/mobiads/MobiAdsTabsActivity.java b/Android/MobiAdsTabs/src/de/android/mobiads/MobiAdsTabsActivity.java
new file mode 100644 (file)
index 0000000..47e47b5
--- /dev/null
@@ -0,0 +1,199 @@
+package de.android.mobiads;
+
+import de.android.mobiads.list.MobiAdsListFragment;
+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;
+import android.widget.Toast;
+
+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<MobiAdsListFragment>(
+                        this, "localads", 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) {
+            Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
+        }
+    }
+    
+    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) {
+                       // TODO Auto-generated method stub
+                       
+               }
+
+               @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/MobiAdsTabs/src/de/android/mobiads/batch/MobiAdsBatch.java b/Android/MobiAdsTabs/src/de/android/mobiads/batch/MobiAdsBatch.java
new file mode 100644 (file)
index 0000000..4b59d62
--- /dev/null
@@ -0,0 +1,324 @@
+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 {
+                                                                       Log.i("MobiAdsBatch","delete");
+                                                                       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("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/MobiAdsTabs/src/de/android/mobiads/list/AdsEntry.java b/Android/MobiAdsTabs/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/MobiAdsTabs/src/de/android/mobiads/list/AdsEntryAdapter.java b/Android/MobiAdsTabs/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/MobiAdsTabs/src/de/android/mobiads/list/MobiAdsListFragment.java b/Android/MobiAdsTabs/src/de/android/mobiads/list/MobiAdsListFragment.java
new file mode 100644 (file)
index 0000000..751f85c
--- /dev/null
@@ -0,0 +1,191 @@
+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 android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.app.Fragment;
+import android.app.Notification;
+import android.app.NotificationManager;
+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.os.Bundle;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import de.android.mobiads.MobiAdsService;
+import de.android.mobiads.R;
+import de.android.mobiads.provider.Indexer;
+
+public class MobiAdsListFragment extends Fragment {
+       private static final String TAG = "MobiAdsListFragment";
+       private AdsEntryAdapter newsEntryAdapter;
+
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+       View v = inflater.inflate(R.layout.mobiadslist, container, false);
+                   
+        // Setup the list view
+        final ListView newsEntryListView = (ListView) v.findViewById(R.id.list);
+        newsEntryAdapter = new AdsEntryAdapter(getActivity(), R.layout.news_entry_list_item);
+        newsEntryListView.setAdapter(newsEntryAdapter);
+        
+        this.registerForContextMenu(newsEntryListView);        
+        
+        newsEntryListView.setOnItemClickListener(new OnItemClickListener() {
+
+                       @Override
+                       public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                               Uri uri = Uri.parse(newsEntryAdapter.getItem(position).getURL());
+                               startActivity(new Intent(Intent.ACTION_VIEW, uri));
+                       }
+        });
+
+       
+        return v;
+    }
+    
+    @Override
+    public void onResume() {
+       // 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... :/
+        // Using FragmentList?
+        for(final AdsEntry entry : getAdsEntries()) {
+               newsEntryAdapter.add(entry);
+        }
+        super.onResume();
+    }
+    
+    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 = getActivity().getContentResolver().query(uri, null, null, null, null);
+       try {
+                       if (cursor.moveToFirst()) {
+                               do {
+                                       values.clear();
+                                       Bitmap bitMap = null;
+                                       FileInputStream file = null;
+                                       try {
+                                               file = getActivity().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)));
+                                               getActivity().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;
+    }
+
+    
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, v, menuInfo);
+        MenuInflater inflater = getActivity().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 = this.newsEntryAdapter.getItem(position);
+        int idAd = entry.getIdAd();
+        Uri uriDelete = Uri.parse("content://" + "de.android.mobiads.provider" + "/" + "indexer" + "/idad/" + idAd);
+       
+        getActivity().getContentResolver().delete(uriDelete, null, null);
+        this.newsEntryAdapter.remove(entry);
+        this.newsEntryAdapter.notifyDataSetChanged();    
+    }
+    
+    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);
+    }
+}
diff --git a/Android/MobiAdsTabs/src/de/android/mobiads/list/MobiAdsNewAdsActivity.java b/Android/MobiAdsTabs/src/de/android/mobiads/list/MobiAdsNewAdsActivity.java
new file mode 100644 (file)
index 0000000..936278c
--- /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.mobiadsnewadslist);
+               
+               // Setup the list view
+               final ListView newsEntryListView = (ListView) findViewById(R.id.list);
+               final AdsEntryAdapter newsEntryAdapter = new AdsEntryAdapter(this, R.layout.news_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/MobiAdsTabs/src/de/android/mobiads/provider/Indexer.java b/Android/MobiAdsTabs/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/MobiAdsTabs/src/de/android/mobiads/provider/IndexerOpenHelper.java b/Android/MobiAdsTabs/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/MobiAdsTabs/src/de/android/mobiads/provider/IndexerProvider.java b/Android/MobiAdsTabs/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