--- /dev/null
+<?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
--- /dev/null
+/** 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
--- /dev/null
+/* 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;
+ }
+}
--- /dev/null
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
--- /dev/null
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-15
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+// }
+ }
+
+}
--- /dev/null
+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
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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/");
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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