The Android File System

scacchicgardenSoftware and s/w Development

Dec 13, 2013 (3 years and 3 months ago)

111 views

The Android File System


Onboard:

Linux Architecture


User privileges


Initially quite limited; some directories hidden


Rooting gives users super user access

o
Procedure is different for different devices

o
Destroying the operation of the device: bricking


Onboard data:
Applications have their reserved storage areas (sandbox)


External data


SD card or USB connection


Public shared: /mnt/sdcard/


Writing to external storage has no security protection


Managing Data (Alternatives)


Application Direct Access:
Read only from res/raw or assets directories


Web
-
based
: Interact through web
-
URLs to access cloud
-
based data


Direct File I/O:

Read/write files onboard or on SD cards


Use Standard Java stream and Random Access File classes


Restriction:
Onboard file I/O restricted to application sandbox


Restriction:
SD card writes requires access permission


Preferences:

Key/Value pairs of data


Database Tables:

Use the built
-
in SQL
-
Lite database facility


Increase functionality:


Content Providers
: expose data to other applications


Services
: background processes that run detached from any view

Application Direct Access


Static application files


Custom codecs that are not widely supported


XML
-
based configuration data


Store either in res/raw or in assets


res/raw enables creating sub
-
directories for specific device configurations


Files stored either in res/raw or in assets are not pre
-
compiled by Android


Files stored directly in the application are read
-
only and cannot be modified


Access using standard Java I/O operations


InputStream is = app.getResources().openRawResource(R.raw.foo);

in = BufferedReader in = new BufferedReader(new InputStreamReader(is)));


InputStream rawRes = context.getAssets().open(“foo.bar");

Reader r = new BufferedReader(new InputStreamReader(rawRes, "UTF8"));



Note
: Use lower case
alphanumerics

to name files in res/raw

Downloading Files


Downloading quickly degrades battery life


Solutions


Pre
-
fetch: A single large download has less impact
than multiple smaller downloads


Reuse existing connections rather than
reestablishing them


Schedule regular downloads at the longest
intervals that are possible


Bundle non
-
time sensitive requests together

Download XML from Server

String myFeed = getString(R.string.my_feed);
// HTTP Web address

try

{

URL url = new URL(myFeed); // Create a new HTTP URL connection


URLConnection connection = url.openConnection();


HttpURLConnection httpConnection = (HttpURLConnection)connection;

int responseCode = httpConnection.getResponseCode();



if (responseCode == HttpURLConnection.HTTP_OK)


{

InputStream in = httpConnection.getInputStream();



processStream(in);
// Use standard Java DOM parsing classes


}

} catch (MalformedURLException e) { Log.d(TAG, "Malformed URL", e); }


catch (IOException e) { Log.d(TAG, "IO Exception.", e); }

Requires:
<uses
-
permission
android:name
= "
android.permission.INTERNET

" />

Download Using Download Manager

DownloadManager m= (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);

long myReference = m.enqueue(new Request(Uri.parse(R.string.webLoc)));

BroadcastReceiver receiver = new BroadcastReceiver()


{ @Override public void onReceive(Context context, Intent intent)


{

long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID,
-
1);



if (myReference == reference)




{

Query query = new Query();




Cursor cursor = downloadManager.query(query.setFilterById(reference));




if (cursor.moveToFirst())




{ int fileX = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);





int uriX = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);




String fileName = cursor.getString(fileX), fileUri = cursor.getString(uriX);






// TODO Do something with the file.




}




cursor.close();

} } };

registerReceiver(receiver,



new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) );


Note:
The cursor object contains
information about downloaded files

Listen for Downloaded Files

<
receive
r android:name=".DownloadReceiver" android:exported="true"

android:icon="@drawable/download_icon" >

<intent
-
filter>

<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>

</intent
-
filter></
receiver
>


IntentFilter f
= new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

registerReceiver(receiver, f); }

BroadcastReceiver receiver
= new BroadcastReceiver()

{

@Override public void onReceive(Context context, Intent intent)


{

String id = DownloadManager.EXTRA_DOWNLOAD_ID;



long[] references = intent.getLongArrayExtra(id);
// Array of
downloaded file ids



for (long reference : references)



{

String mime = getMimeTypeForDownloadedFile (reference);




{
// Handle files that this activity recognizes.

}

}}} ;



Specifying Download Locations


Default Location:

A shared download cache with system generated file
names


Overriding the download location requires:



<uses
-
permission
android:name

=




"
android.permission.WRITE_EXTERNAL_STORAGE
" />


To designate an arbitrary path in external storage


Request
request

= new Request(
uri
);



request.setDestinationUri
(
Uri.fromFile
(f));


To designate a standard external download folder for the application



request.setDestinationInExternalFilesDir
(this,




Environment.DIRECTORY_DOWNLOADS
,
fileName
);


To designate location which shares with music players



request.setDistinationInExternalPublicDir




(
Environment.DIRECTORY_MUSIC
,
fileName
);

Note:

We cannot download to internal storage using
DownloadManager

SD Card File IO


SD Card path:

getExternalStorageDirectory()


How:

Use standard Java File I/O


Notes:


Manifest:

"android.permission.WRITE_EXTERNAL_STORAGE"


Application manifests can set preference to SD Card installation


Check SD Availability in Java:
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)


Convention:

write to
/Android/data/<package_name>/files/


Example


File sdCard = Environment.getExternalStorageDirectory();

File dir = new File (sdcard.getAbsolutePath()



+ "

/Android/data/<package_name>/files/
");

dir.mkdirs();
// Make directories with missing parents.

File file = new File(dir, "filename");
// Instantiate a file object


FileOutputStream f = new FileOutputStream(file);

// Open for writing




Writing to Internal Storage


Writing to internal storage


Each application has a designated directory to which to write files


There is a maximum storage amount, varying per device


Flags exist to control inter
-
application access


MODE_PRIVATE

-

No access for other applications


MODE_WORLD_READABLE

-

Read access for other applications


MODE_WORLD_WRITABLE

-

Write access for other applications


MODE_WORLD_READABLE

|
MODE_WORLD_WRITABLE

-

Read / Write access


Open file for writing


new
BufferedWriter
(



new
OutputStreamWriter
(




openFileOutput
(
fileName
,
MODE_PRIVATE
)));



Onboard File I/O


Write to a designated place for each application


Where
: /data/data/package/files/ using onboard storage


How
: Use standard
java.io

classes, with relative, not absolute, paths


Applications can:

o
Write into its application directory

o
Create subdirectories

o
Give read/write permissions to other applications

(MODE_PRIVATE, MODE_WORLD_READABLE | MODE_WORLD_WRITABLE)


Android Helper classes in the Context object


getDir(String fileName, int permissions): Creates or access directory


getFilesDir(): Get absolute path to application directory


getCacheDir(): Get non
-
permanent temporary storage directory


openFileInput(String fileName): Open a file for input


openFileOutput(String fileName, int permissions): Create a new file




Note:
There is also a
sharedPreferences

directory for preferences,
a databases directory for
SQLite

tables, and a cache directory

Copy File from Server

public void DownloadFile(String fileURL, String fileName)

{


try {


HttpURLConnection c = (HttpURLConnection) fileURL.openConnection();


c.setRequestMethod("GET");


c.setDoOutput(false);
// Tru for “POST” with intent to send to server


c.connect();

// Establish connection with server


InputStream in = c.getInputStream();
// Ready to read



File root = Environment.
getExternalFilesDir
(null);
// Application area



// Prepare output stream



FileOutputStream out = new FileOutputStream(new File(root, fileName));


byte[] buffer = new byte[1024];


int len = 0;
// Note the buffer overflow possibility


while ((len = in.read(buffer)) > 0) {


out.write(buffer, 0, len);


}

// Write block



f.close();

}

}


}

catch (Exception e) {


Log.d("Downloader", e.getMessage());


}

Preferences


Initial Purpose
:


Persistent storage for user options


Analogous to cookies in Web
-
based applications


More General Purpose:


Maintains the application state across terminations and restarts


Provides mechanism for applications to persistently store
additional data without using SQL or Files


Preferences Framework

1.
Define preferences

2.
Display settings

3.
Persist user selections

Accessing Shared Preference Data

// getPreferences is a method in the Activity class

SharedPreferences prefs = getPreferences(Activity.MODE_PRIVATE);


int intValue = getInt("IntKey", 22);

long longValue = getLong("LongKey", 0);

String name = getString("NameKey", "");

int floatValue = getFloat("FloatKey", 0.0);

int booleanValue = getBoolean("BoolKey", false);

Set<String> set = getStringSet("SetKey", 22);

Map<String, ?> keysAndValues = getAll();

Notes:

1.
The second argument is the default if
the key was not previously stored

2.
There is no
getDouble
() method

Modifying SharedPreference Data

SharedPreferences

preferences =
getPreferences
(
Activity.MODE_PRIVATE
);

// or
getSharedPreferences
("
fileName
");

SharedPreferences.Editor

editor =
preferences.edit
();

editor.putInt
("
IntKey
", 22);

e
ditor.putLong
("
LongKey
", 0);

editor.putString
("
NameKey
", "");

editor.putFloat
("
FloatKey
", 0.0);

editor.putBoolean
("
BoolKey
", false);

editor.commit
();



Notes:

1.
puts can be chained:
editor.putInt
(“
intKey
”, 22).
putLong
(“
LongKey
”, 0);

2.
editor.apply
() works asynchronously,
editor.commit
() is synchronous

3.
editor.clear
() erases all of the preference keys

4.
editor.remove
(“Key”) deletes a preference

5.
Shared preferences use a file name for multi
-
activity sharing


Preference Views


Preference Screen


The view on the left


View on the right appears
When the user clicks after
preference screen clicked


Selection Screen


Modal Dialog on the right


Saves selection and disappears
after a user selection

Step 1: Defining Preferences

<?xml version="1.0" encoding="utf
-
8"?>
<!
--

/res/xml/flightoptions.xml
--
>

<
PreferenceScreen

xmlns:android="http://schemas.android.com/apk/res/android"


android:
key
="flight_option_preference"


android:
title
="@string/PrefTitle" android:
summary
="@string/PrefSummary">


<
ListPreference

android:key="@string/SelectedSortOption"



android:
title
="@string/ListTitle" android:summary="@string/ListSummary"



android:
entries
="@array/FlightSortOptions"

android:
entryValues
="@array/FlightSortValues"



android:
dialogTitle
="@string/ChooseFlightOptions"



android:
defaultValue
="@string/SortDefaultValue" />

</PreferenceScreen
>

public class FlightPreferenceActivity extends PreferenceActivity

{

@Override protected void onCreate(Bundle savedInstanceState)


{

super.onCreate(savedInstanceState);



addPreferencesFromResource(R.xml.flightoptions);

}

}

XML Listing (in res/xml) for the preferences shown on the previous screen

Define Strings in res/values

<?xml version="1.0" encoding="utf
-
8"?>

<resources>


<string name="
AppName
">Preferences Demo</string>


<string name="
PrefTitle
">My Preferences</string>


<string name="
PrefSummary
">Set Search Options</string>


<string name="
ListTitle
">Flight Options</string>


<string name="
ListSummary
">Set Search Options</string>


<string
-
array name="
FlightSortOptions
">



<item>Total Cost</item><item># of Stops</item><item>Airline</item></string
-
array>


<string
-
array name="
FlightSortValues
">



<item>0</item><item>1</item><item>2</item></string
-
array>


<string name="
ChooseFlightOptions
">Choose Flight Options</string>


<string name="
SortDefaultValue
">1</string>


<string name="
SelectedSortOption
">Selected Sort Option</string>


<string name="
menu_prefs_title
">Settings</string>


<string name="
menu_quit_title
">Quit</string>

</resources>

Manifest

<?xml version="1.0" encoding="utf
-
8"?> <!
--

AndroidManifest.xml
--
>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"


package="com.syh" android:versionCode="1" android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/AppName">

<activity android:name=".
MainActivity
" android:label="@string/AppName">


<intent
-
filter><action android:name="android.intent.action.MAIN" />



<category android:name="android.intent.category.LAUNCHER" />


</intent
-
filter>

</activity>

<activity android:name=".
PreferenceActivity
" android:label="@string/PrefTitle">

<intent
-
filter><action android:name="com.syh.intent.action.
FlightPreferences
" />


<
category

android:name="
android.intent.category.PREFERENCE
" />

</intent
-
filter>

</activity></application><uses
-
sdk android:minSdkVersion="3" /></manifest>

Define Main Android Activity

// This is MainActivity.java

@Override public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);


setContentView(R.layout.main);
// Assuming there is a TextView layout


tv = (TextView)findViewById(R.id.text1);


setOptionText();
// Helper method to get user preference and adjust view

}

@Override public boolean onCreateOptionsMenu(Menu menu)

{

MenuInflater inflater = getMenuInflater();


// Inflate main menu XML with link to preference option


inflater.inflate(R.menu.mainmenu, menu);


return true;

// Menu is now active

}

Main Activity Selection Responses

@Override public
boolean

onOptionsItemSelected

(
MenuItem

item)

{

if (
item.getItemId
() ==
R.id.menu_prefs
)


{

Intent
intent

= new Intent().
setClass
(this,
com.syh.
PreferenceActivity
.class
);



this.start
ActivityForResult
(intent, 0);
// Second parameter is for a switch in listener


} else if (
item.getItemId
() ==
R.id.menu_quit
) { finish(); }


return true;

// Start preference menu when its item is selected

}

@Override public void
onActivityResult
(
int

reqCode
,
int

resCode
, Intent data)

{
super.onActivityResult
(
reqCode
,
resCode
, data);


SharedPreferences

prefs

=
getDefaultSHaredPreferences
(this);


String key =
this.getResources
().
getString
(
R.string.SelectedSortOption
);


String default =
this.getResources
().
getString
(
R.string.SortDefaultValue
);


String selection =
prefs.getString
(key , default);


String[]
optionText

=
this.getResources
().
getStringArray
(
R.string.FlightSortOptions
);


tv.setText
("option = " +
pref

+ " (" +

optionText
[
Integer.parseInt
(
pref
)] + ")");

}


CheckBoxPreference
(/res/xml/chkbox.xml)

<?xml version="1.0" encoding="utf
-
8"?>

<PreferenceScreen


xmlns:android ="http://schemas.android.com/apk/res/android"



android:key="flight_columns_pref">


<CheckBoxPreference android:key="show_airline_column_pref"



android:title="Airline" android:summary="Show Airline column" />


<CheckBoxPreference android:key="show_departure_column_pref"



android:title="Departure" android:summary="Show Departure column" />


<CheckBoxPreference android:key="show_arrival_column_pref"



android:title="Arrival" android:summary="Show Arrival column" />


<CheckBoxPreference android:key="show_total_travel_time_column_pref"



android:title="Total Travel Time"android:summary="Show Total Travel Time column" />


<CheckBoxPreference android:key="show_price_column_pref"



android:title="Price" android:summary="Show Price column" />

</PreferenceScreen>

Note:

For Radio Buttons, use the List Preference

Using Check Box Preferences

// CheckBoxPreferenceActivity.java

import android.os.Bundle;

import android.preference.PreferenceActivity;

public class CheckBoxPreferenceActivity extends PreferenceActivity

{


@Override protected void onCreate(Bundle savedInstanceState)


{



super.onCreate(savedInstanceState);



addPreferencesFromResource(R.xml.chkbox);


}

}


// Access preference in an activity

SharedPreferences prefs = getSharedPreferences("fileName");

Boolean option = prefs.getBoolean("show_price_column_pref", false);


EditTextPreference

<?xml version="1.0" encoding="utf
-
8"?>

<PreferenceScreen xmlns:android=
http://schemas.android.com/apk/res/android


android:title=“Set Package Name"


android:summary="Set the package name for generated code">

<EditTextPreference android:key="package_name_preference"


android:title="Set Package Name" android:dialogTitle="Package Name"


android:summary="Set the package name for generated code" />

</PreferenceScreen>

Categories of Preferences

<?xml version="1.0" encoding="utf
-
8"?>

<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"


android:key="using_categories_in_root_screen" android:title="Categories"


android:summary="Using Preference Categories">

<PreferenceCategory
xmlns:android=
http://schemas.android.com/apk/res/android


android:key="meats_category" android:title="Meats" android:summary="Meat preferences">


<CheckBoxPreference android:key="fish_selection_pref" android:title="Fish"



android:summary="Fish is great for the healthy" />


<CheckBoxPreference android:key="chicken_selection_pref" android:title="Chicken"



android:summary="A common type of poultry" />


<CheckBoxPreference android:key="lamb_selection_pref" android:title="Lamb"



android:summary="Lamb is a young sheep" />
</PreferenceCategory>

<PreferenceCategory
xmlns:android=
http://schemas.android.com/apk/res/android


android:key="vegi_category" android:title="Vegetables" android:summary="Vegetable preferences">


<CheckBoxPreference android:key="tomato_selection_pref" android:title="Tomato "



android:summary="It's actually a fruit" />


<CheckBoxPreference android:key="potato_selection_pref" android:title="Potato"


android:summary="My favorite vegetable" />
</PreferenceCategory></PreferenceScreen>

Purpose
: Provide titles to groups of similar preferences

Preference Fragments


Android versions 3.0 and newer recommend using fragments
for preferences instead of activities



Check Android Version:

if (
Build.VERSION.SKK_INT
<
Build.VERSION_CODES.HONEYCOME
)



{

/* Use
p
reference activity approach */ }


else

{ /* Use
p
reference fragment approach */ }



Implementation Steps

1.
Define preference screens in XML (as shown on previous slides)

2.
Add
PreferenceFragment

to the activity using the
FragmentManager

3.
Create the
PreferenceFragment

class



Preference Fragment in an Activity

public class PreferenceFragmentActivity extends Activity

{

@Override


public void onCreate(Bundle savedInstanceState)


{

super.onCreate(savedInstanceState);



setContentView(R.layout.main);




FragmentManager manager = getFragmentManager();



FragmentTransaction trans = manager.beginTransaction();



MyFragment fragment = new MyFragment();



trans.replace(android.R.id.content, fragment1);



trans.addToBackStack(null);



trans.commit();


}

}

Note
: Don’t add the fragment to the application manifest

Preference Fragment Class

public class
MyFragment

extends
PreferenceFragment


{

@Override public void
onCreate
(Bundle
savedInstanceState
)


{

super.onCreate
(
savedInstanceState
);




// configure the preferences from an XML file




addPreferencesFromResource
(
R.xml.preferences
);



PreferenceManager

manager =
getPreferenceManager
();



SharedPreferences

shared =
manager.getSharedPreferences
();



shared.registerOnSharedPreferenceChangeListener
(this);


}


onSharedPreferenceChanged
(
SharedPreferences

pref
, String key)


{ /* Handle preference change here */

}

}

SQLite


Android SQLite:


Lightweight database system


Standards
-
compliant


Single Tier (no server involved)


Open source


Standard SQL queries

“SELECT ID, CITY, STATE FROM STATION
WHERE
LAT_N > 39.7
;”


Application content providers provide


Standard interface to SQL tables to other applications


For application private tables, content providers are not needed

Authorities / URI


Definition
:

An authority is a registered name on an android device (ex:
Content Provider name. Authorities must be unique on the system.


Registration
:
Accomplished in an application manifest


Syntax
:
com.company.someProvider


Examples
:
org.acorns.LessonProvider or com.android.provider.BookProvider


Analogy
:
Authorities are to Android what domain names are to the Web


Activity access using URIs


Syntax:

content://authority
-
name/path
-
segment/path
-
segment/…


Access book table:
content://com.anroidbook.provider.BookProvider/books


Access particular book:


content://com.anroidbook.provider.BookProvider/books/12


Activity Interface:

Through Content Providers, which fill in abstract methods

Purpose:
Publish application SQL tables and data for external activity access

MIME Types


Definition
:
Industry standard way to indicate a particular data format


Syntax
:
Two text strings separated by slashes


First part (category)
:
application, audio, image, text, video, etc


Second Part (codec)
:
html, css, xml, pdf, rtf, etc.


Examples
:
text/htm, application/pdf, image/jpeg, audio/mpeg, etc.


Android MIME Types

(Similar syntax as standard MIME types)


First Part

o
Access single items:
vnd.android.cursor.item

o
Access multiple items:

vnd.android.cursor.dir


Second Part:

vnd.yourCompany.type


Examples:
vnd.android.cursor.dir/vnd.google.note or
vnd.android.cursor.item/vnd.google.note

Purpose: Indicate a type of data that a content provider deals with

Content Provider Framework


An application registers its content provider using its manifest


The code for the content provider is written


An application request data using the published MIME type


Android looks through the registered manifests for an appropriate
provider


The provider activity is launched to manipulate or return data


If more than one provider exists, the user selects the one to use


Android Built
-
in content providers:
content://media/internal/images/,
content://media/external/images/,

content://contacts/people/

Content Providers


Register an authority using the AndroidManifest.xml file


Definition:

An authority is a registered name


Analogy:

authority is to an Android device what a domain name is
to the Internet


Syntax
:
( Using a package name minimizes redundancies on a system)

<provider android:name=
"
fooProvider
"


android:authorities=
"com.company.fooProvider" />


Example
:

<provider android:name=
" com.acorns.
LessonProvider
"


android:authorities=
"com.acorns.provider.Lessons" />


Another application example
:

<provider android:name=
“NotePad
Provider
"


android:authorities=
"com.google.provider.NotePad" />


A well
-
defined interface to data for application access

MIME Types


Specifies the data type an activity can process


Register in the AndroidManifest.xml file


Example:
"vnd.android.cursor.dir/vnd.google.note"

OR
"vnd.android.cursor.dir/vnd.google.note"


Sample Syntax
:
vnd.android.cursor.<opt>/vnd.company.type


vnd: non
-
standard type (vendor)


android.cursor: required for ContentProvider Manifest registrations


item: single record; dir: collection of records


google: company name


note: content type


AndroidManifest.xml registration

<data android:mimeType=
"vnd.android.cursor.dir/vnd.google.note" />


Acronym:
Multipurpose Internet Mail Extension

Referring to a Content Provider


URI and URL


Uniform resource identifier (URI) Identifies the name of a resource, but not
necessarily information regarding its location


Uniform resource Location (URL) is a URI, that identifies a resource location


Text usage
: URI for content providers; URL for referring to Web locations


URI of a content provider

content://com.company.fooProvider/

content://com.acorns.provider.Lessons/

content://com.google.provider.NotePad/


Note:

Android provided shortcut URIs:
contacts

for
com.google.android.contacts


Refer to the notes SQLite notes table

content://com.google.provider.NotePad/notes/


Refer to the tenth note

content://com.google.provider/NotePad/notes/10

Register the Provider


<provider android:name=
"NotePadProvider"


android:authorities=
"com.google.provider.NotePad"

/>


<activity android:name=
"NotesList" android:label="@string/title_notes_list">

<intent
-
filter>


<action android:name=
"android.intent.action.MAIN" />


<category android:name=
"android.intent.category.LAUNCHER" />

</intent
-
filter>

<intent
-
filter>


<action android:name=
"android.intent.action.VIEW" />


<data android:mimeType=
"vnd.android.cursor.dir/vnd.google.note" />

</intent
-
filter><intent
-
filter>


<action android:name=
"android.intent.action.GET_CONTENT" />


<data android:mimeType=
"vnd.android.cursor.item/vnd.google.note" >

</intent
-
filter> </activity>

Implement the Provider


Plan the Database


Extend the ContentProvider class


Fill in a variety of methods to be overloaded


onCreate(): When the database is created


onUpgrade(): When the database layout changes


getType(): Return the MIME data type


query(): Handle queries to the data


insert(), delete(), and update() methods

A Managed Query in Content Provider

// Specify needed columns

string[] projection = new string[]


{ People._ID, People.NAME, People.NUMBER };


// Specivy URI using static Contacts constants

Uri contactUri = Contacts.People.CONTENT_URI;


// Invoke a content provider query

Cursor cursor = managedQuery( contactUri,

projection,

//Which columns to return

null, null,

// Selection and WHERE clauses

Contacts.People.NAME + " ASC");
// Order
-
by (ascending by name)


Purpose
: Query SQL lite table for relevant data (cursor)

Definition
: An Android cursor is a collection of rows of data

Other classes exist (ex:
SQLiteQueryBuilder
) for using standard SQL strings

Navigating through a cursor


Manipulate the cursor (row pointer)


if (cursor.moveToFirst() == false)


cursor.isBeforeFirst(), cursor.isAfterLast, cursor.isClosed()


while (cursor.moveToNext()) { /* code here */ }


for (cursor.moveToFirst(); !cursor.isAfterLast(); cur.moveToNext) { … }


Get column numbers from names


int nameColumn = cursor.getColumnIndex(People.NAME);


int phoneColumn = cursor.getColumnIndex(People.NUMBER);


Get Data from column


String name = cursor.getString(nameColumn);


String number = cursor.getString(phoneColumn);


Prerequisites:
Know column names, data types, column name to index
relationship


After a query, the cursor points before the first; use
moveToFirst
(); use
moveToNext
() for the initial access

Other Access Methods


Add

ContentValues values = new ContentValues();

values.put(BookProviderMetaData.BookTableMetaData.BOOK_NAME, "book1");

values.put(BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR, "author
-
1");

ContentResolver resolve = context.getContentResolver();

URI uri = BookProviderMetaData.BookTableMetaData.CONTENT.URI;

Uri inserted = resolve.insert(uri, values);


Delete:

Assume that bookIndex is pre
-
defined

ContentResolver resolve = context.getContentResolver();

URI uri = BookProviderMetaData.BookTableMetaData.CONTENT.URI;

Uri delURI = uri.withAppendedPath(uri, Integer.toString(bookIndex));

resolve.delete(delUri, null, null);


Count

URI uri = BookProviderMetaData.BookTableMetaData.CONTENT.URI;

Cursor cursor = activity.managedQuery(uri, null, null, null, null);

Int numberOfRecords = cursor.getCount();

cursor.close();

See next slide for
symbolic constant
definitions

BookProviderMetaData class

Content Resolver


Definition
:
A Content Resolver is an Android class that
matches the URI to an available Content Provider that can
handle the data


Purpose:
Separate the provider from the data, enabling the
possibility of multiple content providers available to handle
the same data types


Access:
Use a collection of method calls to insert, retrieve,
delete, update data records


Example
: (insert a new note into the notepad)



ContentResolver resolver = activity.getContentResolver();



Uri newUri = resolver.insert(Notepad.Notes.CONTENT_URI, values);


Note:

values is an instance of the
ContentValues

class

Store Files in an SQLite Database

Steps

1.
Create the
SQLite

table with a column called _data

2.
Get a writeable output stream

3.
Write the file to the reserved column name

_data


I/O Example



//Use a content resolver to insert the record


ContentResolver

contentResolver

=
activity.getContentResolver
();


Uri
newUri

=
contentResolver.insert
(
Notepad.Notes.CONTENT_URI
, values);


//Use the content resolver to get an output stream directly



OutputStream

outStream

=
contentResolver
().
openOutputStream
(
newUri
);


writeFileToRecord
(
outStream
);


outStream.close
();

ContentValues

object
contains a set of values

Note
: The data is actually stored in a
separate area. The _data column contains
the Uri reference to the data

DataBase Helper


Purpose


Manage database creation and version management


Responsible for creating and upgrading an SQLite database


Defer database opening and updating to the first use


Avoids blocking application startup with compute intensive database
upgrades


Contains methods for getWritableDatabase and
getReadableDatabase


Simplifies Content Provider access to files


Eliminates concern over whether the application was terminated



Implementation


Create a class that extends SQLiteOpenHelper


Override onCreate() and onUpgrade() methods

Helper Code
(Two tables: Employees, Department)

public class DatabaseHelper extends
SQLiteOpenHelper

{ static final String dbName="demoDB";



static final String employees="Employees";


static final String colID="EmployeeID";


static final String colName="EmployeeName";


static final String colAge="Age";


static final String colDept="Dept";



static final String depts="Department";


static final String colDeptID="DeptID";


static final String colDeptName="DeptName";


static final String viewEmps="ViewEmps";



public DatabaseHelper(Context context)


// Constructor


{ super(context, dbName, null
/* Extension to cursor class */
, 1
/* version */
); }

This application will need to override
onCreate

and
onUpgrade

Helper Example (cont.)

public void onCreate(SQLiteDatabase db)

{

db.execSQL
("CREATE TABLE " + "depts +


" (" + colDeptID +" INTEGER PRIMARY KEY , " +

colDeptName+ " TEXT)");



db.execSQL
("CREATE TABLE "+employees +


" ("+ colID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +


colName + " TEXT, "+ colAge + " Integer, " +


colDept + " INTEGER NOT NULL , FOREIGN KEY ("+ colDept +") REFERENCES " +


deptTable+" ("+colDeptID+"));");



db.execSQL
("CREATE TRIGGER fk_empdept_deptid "+" BEFORE INSERT ON "+employees+


" FOR EACH ROW BEGIN" + " SELECT CASE WHEN ( (SELECT " + colDeptID + " FROM " +


depts + " WHERE " + colDeptID + "=new." + colDept + " ) IS NULL)" +



" THEN RAISE (ABORT, 'Foreign Key Violation') END;" + " END;");




db.execSQL
("CREATE VIEW "+ viewEmps + " AS SELECT " + employees + "." + colID +


" AS _id," + " " + employees + "." + colName + ", " + employees + "." + colAge + ", " +


depts + "." + colDeptName + "" + " FROM "+ employees + " JOIN " + depts +


" ON " + employees +"." + colDept+" =" + depts + "." + colDeptID );

}

Helper Example (cont.)
Upgrading the DB

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)

{


db.execSQL("DROP TABLE IF EXISTS "+employees);


db.execSQL("DROP TABLE IF EXISTS "+depts);



db.execSQL("DROP TRIGGER IF EXISTS dept_id_trigger");


db.execSQL("DROP TRIGGER IF EXISTS dept_id_trigger22");


db.execSQL("DROP TRIGGER IF EXISTS fk_empdept_deptid");


db.execSQL("DROP VIEW IF EXISTS "+viewEmps);


onCreate(db);

}

Called when the version number in the constructor changes

Note:
The activity can respond to menu selections to insert, remove, and update rows

Using a Database helper

Helper = new Helper(context, Helper.DATABASE_NAME, null, Helper.VERSION);

SQLiteDatabase db = Helper.getWritableDatabase();
// Perform query

Cursor cursor = db.query(Helper.DATABASE_TABLE, result_columns, where,



whereArgs, group, having, order);


SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();

// Insert row

db.insert(HoardDBOpenHelper.DATABASE_TABLE, null, newValues);


SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase
(); // Update row

db.update(Helper.DATABASE_TABLE, newValues, where, whereArgs);


SQLiteDatabase db = Helper.getWritableDatabase();
// Delete matching rows

db.delete(Helper.DATABASE_TABLE, where, whereArgs);


Find media titles and albums

String[] projection = {

MediaStore.Audio.AudioColumns.ALBUM,


MediaStore.Audio.AudioColumns.TITLE };




Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;

Cursor cursor = getContentResolver().query(uri, projection, null, null, null);

int aX= cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM);

int tX = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE);

String[] result = new String[cursor.getCount()];

while (cursor.moveToNext())

{

String title = cursor.getString(tX), album =
cursor.getString(aX);


result[cursor.getPosition()] = title + " (" + album + ")";

}




cursor.close();


Using the Android provided Media Store Content Provider

Other Native Content Providers:
Browser, Contacts, Calendar, Call log

Accessing the Contact List

int iDCol = ContactsContract.Contacts.ID;

int nameCol = ContactsContract.Contacts.DISPLAY_NAME;

Uri uri = ContactsContract.Contacts.CONTENT_URI;

String[] projection = { iDCol, nameCol };

Cursor cursor = getContentResolver().query(uri, projection, null, null, null);

int nameIndex = cursor.getColumnIndexOrThrow(nameCol);

int idIndex = cursor.getColumnIndexOrThrow(contactIDCol);

String[] result = new String[cursor.getCount()];

while(cursor.moveToNext())

{

String name = cursor.getString(nameIndex);


String id = cursor.getString(idIndex);


result[cursor.getPosition()] = name + " (" + id + ")";

}

cursor.close();

Searchable Applications


Expose the application to search
-
facilities


Alternatives


Search bar appearing after hardware search button
pressed or a program method call



Search widget placed somewhere on the application
view



Quick Search implemented as a home screen widget



Enable voice search

Making an Application Searchable


In the manifest


Create an xml file with the searchable attributes


Mark the application searchable



Define the intent for the activity processing searches


Either create a search activity


Launch when the search is initiated


Process and display the results


Or use a search dialog (floating search box)


Hidden by default, but can be initiated by calling
onSearchRequested(), which is an Activity class method


If the device doesn’t contain a dedicated search button, add a menu
option or widget that calls onSearchRequested()


Or use the SearchView widget

Searchable configuration in res/xml

<?xml version="1.0" encoding="utf
-
8"?>

<searchable

xmlns:android
=
http://schemas.android.com/apk/res/android






android:label
="@string/
app_label







android:hint
="@string/
search_hint
" >

</searchable>



The label (required attribute) becomes visible when search is
launched and contains the search application name


The hint appears in the search box before users enter a query. It
contains a hint about what can be searched


Other attributes are for search suggestions and voice search

Filename
: searchable.xml

Searchable launch in Manifest

<application>



<activity android:name=".SearchActivity“




android:launchMode="singleTop">


<intent
-
filter>



<action android:name="android.intent.action.SEARCH" />





</intent
-
filter>




<meta
-
data

android:name="android.app.searchable"







android:resource="@xml/searchable"


/>

</activity>



Note:

singleTop prevents multiple instances on the back stack

Search Activity

@Override

public void onCreate(Bundle savedInstanceState)

{


super.onCreate(savedInstanceState);


setContentView(R.layout.search);



// Get the intent, verify the action and get the query


Intent intent = getIntent();


if (Intent.ACTION_SEARCH.equals(intent.getAction()))


{

String query = intent.getStringExtra(SearchManager.QUERY);



doMySearch(query);
// Process the request


}

}

Services


Services handle background events
separate from a View


Services reside in the main thread
of the launching process! Use
thread facilities to avoid blocking
the main thread


Only foreground activities have a
higher priority, so services are
rarely terminated by the OS


Proper Handling


Main activity makes requests


Service starts worker threads to
perform requests


Bind services to the main
thread


Callbacks to the main activity
completes transactions.




Downloading Threads (maybe thread pool?)

Main Activity

Service

onButtonClick() method
gets control in the
executing activity

Activity uses an intent to request the download
from the Service
!
)

Worker Thread

(downloading)

New worker
thread

Done downloading!

Start new worker thread

User selects item to
download

Creating a Service

1.
Create a class extending the service class

2.
Register the service in the application manifest



<service
android:enabled
="true"


android:name
=".
MyService
"


android:permission
="
foo.bar.MY_SERVICE_PERMISSION
"/>

4.
Start and stop of the service in the main activity

5.
Use separate threads for compute
-
bound operations


Bind service to the main activities so the main activity can
call service class methods and then update its user interface
views

6.
Self
-
terminate the service when it is no longer needed

Starting and Stopping Services


Explicitly start My Service


Intent
intent

= new Intent(this,
MyService.class
);


//
TODO

Add extras if required.


startService
(intent); }


Implicitly start a music Service


Intent intent = new Intent(
MyMusicService.PLAY_ALBUM
);

intent.putExtra
(
MyMusicService.ALBUM_NAME_EXTRA
, "United");


intent.putExtra
(
MyMusicService.ARTIST_NAME_EXTRA
, "
Pheonix
");


startService
(intent);


Explicitly stop My Service

:


stopService
(new Intent(this,
MyService.class
));


Implicitly stop
M
usic Service


stopService
(new Intent(
MyMusicService.PLAY_ALBUM
));

Creating a Bindable Service

public class LocalService extends Service

{



private final IBinder binder = new LocalBinder();



public class LocalBinder extends Binder


{


LocalService getService() {


return LocalService.this;


}


}




@Override public void onCreate()


{ // TODO: Actions to perform when service is created. }



// Return instance after activity call onBindService() call






@Override public IBinder onBind(Intent intent)


{ return binder;


}








/** method for clients */






public int getRandomNumber() { return Math.random();




}

}

Purpose: Enable activities to access service methods

public class
BindingActivity

extends Activity


// An Activity using a Bound Service

{




LocalService

service
;
boolean

bound
= false;







private
ServiceConnection

connection

= new
ServiceConnection
()
// Anonymous instance


{

@Override public void
onServiceConnected
(
ComponentName

name,


IBinder

service)



{

LocalBinder

binder=(
LocalBinder
) service;





service
=
binder.getService
();

bound
=true; }




@Override


public void
onServiceDisconnected
(
ComponentName

arg0)



{

bound
= false; }


};






@Override


protected void
onCreate
(Bundle
savedInstanceState
)


{



super.onCreate
(
savedInstanceState
);

setContentView
(
R.layout.main
); }






@Override protected void
onStart
()


{

super.onStart
();

Intent
intent

= new Intent(this,
LocalService.class
);










bindService
(intent,
c
onnection
,
Context.BIND_AUTO_CREATE
);



}







@Override protected void
onStop
()

`

{

super.onStop
(); if (
bound
) {


unbindService
(
c
onnection
);


bound

= false;




} }







public void
onButtonClick
(View v)


{




if (
bound
) {


int

num

=
s
ervice.getRandomNumber
();










Toast.makeText
(this, "number: "+
num
,
Toast.LENGTH_SHORT
).show(); } }

}

Updating the GUI from a Service

1.
The activity registers a listening object with the service

2.
The service broadcasts an intent and the activity responds

3.
Use a Handler in an activity


private final Handler
h
andler

= new Handler();

// Process queue of
runnables

private
OnClickListener

buttonListener

= new
OnClickListener
()

{

public void
onClick
(View v)


{

new Thread(new Runnable()



{


pubic void run()




{

//.... hard work in separate thread



handler.post
(new Runnable() {

public void run() {

// ... update the U
I

}
});


}).start();


}


};

}


Note
: There are other possibilities. For example, one
could use
AsyncTask
,
IntentService

or Loader objects