ANDROID
ANDROID
When you install an app on your phone, the Android operating system will give
you some form of secret internal storage where the app can store its private data.
No other application has access to this information. When you uninstall an
application, all of the data associated with it is also removed.
To save a file to the internal storage, you must first obtain it from the internal
directory. You can do this by calling the getFilesDir() or getCacheDir() methods.
The getFilesDir() method returns the absolute path to the directory where files are
created on the filesystem. getCacheDir() returns the absolute path to the
filesystem’s application-specific cache directory.
Most Android devices have relatively low internal storage. As a result, we keep
our data on an external storage device. These storage units are accessible to
everyone, which means they can be accessed by all of your device’s applications.
You can also access the storage by connecting your mobile device to a computer.
You must obtain the READ EXTERNAL STORAGE permission from the user in
order to gain access to the external storage. As a result, any application with this
permission has access to your app’s data.
You can use the shared preferences if you only have a little amount of data to
keep and don’t want to use the internal storage. Shared Preferences are used to
store data in a key-value format, which means you’ll have one key and the
associated data or value will be stored depending on that key. The data saved in
the shared preferences will remain with the application until you delete it from
your phone. All shared preferences will be deleted from the device if you
uninstall the application.
Databases are collections of data that are organized and saved for future use.
Using a Database Management System, you can store any type of data in your
database. All you have to do is establish the database and use one query to
perform all of the operations, such as insertion, deletion, and searching. The
query will be passed to the database, which will return the desired output. In
Android, an SQLite database is an example of a database.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Fill Name Then Rotate:"
android:id="@+id/textView2" />
<!-- used to enter the data and get saved in bundle-->
<EditText
android:id="@+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="text"
/>
❘
}
//Restoring the State
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
Log.i(TAG, "onRestoreInstanceState");
final TextView textView =
(TextView) findViewById(R.id.textView);// getting the reference of
textview from xml
CharSequence savedText=
savedInstanceState.getCharSequence("savedText");// getting the text of
editext
Output:
Now run the application in Emulator and enter the name. In our case we have
entered AbhiAndroid.
❘
Now rotate the screen from vertical to horizontal (Press ctrl + F12 in Windows &
Fn+Left CTRL+F12 on Mac to rotate). After you change screen orientation you
will see the same name appear just right side of name.
Now make sure that key name should be unique for any values you save otherwise
it will be overrided. To exactly save the values you put in different primitive
functions you must call commit() function of Editor.
SharedPreferences sp = getSharedPreferences(PREFS_GAME
,Context.MODE_PRIVATE);
sp.edit().putInt(GAME_SCORE,100).commit();
SharedPreferences sp = getSharedPreferences(PREFS_GAME
,Context.MODE_PRIVATE);
int sc = sp.getInt(GAME_SCORE,0);
Log.d("AbhiAndroid","achieved score is "+ sc);
Step 1: Create a new project and create an login Activity activity_login.xml. In this
create a login UI asking user email and password with an option of remember
me checkbox. Also a button displaying Signin or Register.
Below is the complete code login UI activity_login.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
❘
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.samplesharedpreferences.LoginActivity">
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/email_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
❘
android:layout_height="wrap_content">
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:singleLine="true"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_password"
android:imeActionId="@+id/login"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
❘
android:maxLines="1"
android:singleLine="true"/>
</android.support.design.widget.TextInputLayout>
<CheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Remember Me"
android:id="@+id/checkBoxRememberMe"/>
<Button
android:id="@+id/email_sign_in_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in"
android:textStyle="bold"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
Step 2: Now lets code the LoginActivity.java where we will create a Login form.
Below is the complete code of LoginActivity.java with explanation included in
comment.
❘
// UI references.
private EditText mEmailView;
private EditText mPasswordView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Set up the login form.
mEmailView = (EditText) findViewById(R.id.email);
mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new
TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent
keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
❘
});
checkBoxRememberMe = (CheckBox)
findViewById(R.id.checkBoxRememberMe);
//Here we will validate saved preferences
if (!new PrefManager(this).isUserLogedOut()) {
//user's email and password both are saved in preferences
startHomeActivity();
}
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
❘
// Reset errors.
mEmailView.setError(null);
mPasswordView.setError(null);
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}
if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
// save data in local shared preferences
if (checkBoxRememberMe.isChecked())
saveLoginDetails(email, password);
startHomeActivity();
}
}
Step 3: Create a new java class for Shared Preference PrefManager. Below is the
code of PrefManager.java
Step 4: Design the simple Home UI where we will display Welcome message
along with his saved email address in Shared preference. Below is the code of
content_home.xml
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.samplesharedpreferences.HomeActivity"
tools:showIn="@layout/activity_home">
<TextView
android:id="@+id/textViewUser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:text="Welcome"/>
</RelativeLayout>
Output:
Step 1: Now run the App in Emulator. You will see the below output screen
❘
Step 2: Fill the email, password and click on remember me checkbox. In our case
we have filled [email protected] and abhiandroid.
Step 3: It will display you the welcome message along with your email ID. Now
close the App and reopen it. You will see the same message of Welcome and your
email address printed. So here we have stored your email address on your device
itself using Shared Preference.
❘
To make the data private i.e you can use MODE_PRIVATE as discussed below
about the modes.
Technique is best suited when data can only be access by the application neither by
the user nor by other apps.
The data is stored in a file which contains data in bit format so it’s required to
convert data before adding it to a file or before extracting from a file.
Define the filename as string and also define data you wanna write to file as string or
in any format generated from app or any other source.
Use FileOutputStream method by creating its object as defined.
Convert data into byte stream before writing over file because file accepts only byte
format further close the file using file object.
Important Note: It is necessary to add external storage the permission to read and
write. For that you need to add permission in android Manifest file.
Open AndroidManifest.xml file and add permissions to it just after the package
name.
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
❘
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />
} else {
// SD card not mounted
isAvailable = false;
isWritable= false;
isReadable= false; }
.
❘
A content provider behaves very much like a database — you can query it, edit its
content, as well as add or delete its content. However, unlike a database, a content
provider can use different ways to store its data. The data can be stored in a
database, in files, or even over a network.
Android ships with many useful content providers, including the following:
Content URI
❘
content:// – Mandatory part of the URI as it represents that the given URI is a
Content URI.
authority – Signifies the name of the content provider like contacts, browser, etc.
This part must be unique for every content provider.
optionalPath – Specifies the type of data provided by the content provider. It is
essential as this part helps content providers to support different types of data that
are not related to each other like audio and video files.
optionalID – It is a numeric value that is used when there is a need to access a
particular record.
This involves number of simple steps to create your own content provider.
First of all you need to create a Content Provider class that extends the
ContentProviderbaseclass.
Second, you need to define your content provider URI address which will be used to
access the content.
Next you will need to create your own database to keep the content. Usually,
Android uses SQLite database and framework needs to override onCreate() method
which will use SQLite Open Helper method to create or open the provider's
database. When your application is launched, the onCreate() handler of each of its
Content Providers is called on the main application thread.
Next you will have to implement Content Provider queries to perform different
database specific operations.
Finally register your Content Provider in your activity file using <provider> tag.
❘
Following are the six abstract methods and their description which are
essential to override as the part of ContenProvider class:
Abstract
Description
Method
A method that accepts arguments and fetches the data from the
query()
desired table. Data is retired as a cursor object.
To insert a new row in the database of the content provider.
insert()
It returns the content URI of the inserted row.
This method is used to update the fields of an existing row.
update()
It returns the number of rows updated.
This method is used to delete the existing rows.
delete()
It returns the number of rows deleted.
This method returns the Multipurpose Internet Mail
Extension(MIME)
getType()
type of data to the given Content URI.
As the content provider is created, the android system calls
onCreate()
❘
Abstract
Description
Method
this method immediately to initialise the provider.
Accessing a provider
For example, to get a list of the words and their locales from the User Dictionary
Provider, you call ContentResolver.query(). The query() method calls the
ContentProvider.query() method defined by the User Dictionary Provider. The
following lines of code show a ContentResolver.query() call:
query() SELECT
Notes
argument keyword/parameter
Uri maps to the table in the
Uri FROM table_name provider named
table_name.
projection is an array of
columns that should be
projection col,col,col,...
included for each row
retrieved.
selection specifies the
selection WHERE col = value
criteria for selecting rows.
(No exact
equivalent. Selection
selectionArgsarguments replace ?
placeholders in the
selection clause.)
sortOrder specifies the
ORDER BY
sortOrder order in which rows appear
col,col,...
in the returned Cursor.
❘
The first argument to either query() or managedQuery() is the provider URI — the
CONTENT_URI constant that identifies a particular ContentProvider and data set
(see URIs earlier).
To restrict a query to just one record, you can append the _ID value for that record
to the URI — that is, place a string matching the ID as the last segment of the path
part of the URI. For example, if the ID is 23, the URI would be:
content://. . . ./23
import android.provider.Contacts.People;
import android.content.ContentUris;
import android.net.Uri;
import android.database.Cursor;
// Use the ContentUris method to produce the base URI for the contact with _ID ==
23.
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
The other arguments to the query() and managedQuery() methods delimit the query
in more detail. They are:
The names of the data columns that should be returned. A null value returns all
columns. Otherwise, only columns that are listed by name are returned. All the
content providers that come with the platform define constants for their columns.
For example, the android.provider.Contacts.Phones class defines constants for the
names of the columns in the phone table illustrated earlier &mdash _ID,
NUMBER, NUMBER_KEY, NAME, and so on.
A filter detailing which rows to return, formatted as an SQL WHERE clause
(excluding the WHERE itself). A null value returns all rows (unless the URI limits
the query to a single record).
Selection arguments.
A sorting order for the rows that are returned, formatted as an SQL ORDER BY
clause (excluding the ORDER BY itself). A null value returns the records in the
default order for the table, which may be unordered.
Let's look at an example query to retrieve a list of contact names and their primary
phone numbers:
import android.provider.Contacts.People;
import android.database.Cursor;
// Get the base URI for the People table in the Contacts content provider.
Uri contacts = People.CONTENT_URI;
This query retrieves data from the People table of the Contacts content provider. It
gets the name, primary phone number, and unique record ID for each contact. It
also reports the number of records that are returned as the _COUNT field of each
record.
The constants for the names of the columns are defined in various interfaces —
_ID and _COUNT in BaseColumns, NAME in PeopleColumns, and NUMBER in
PhoneColumns. The Contacts.People class implements each of these interfaces,
which is why the code example above could refer to them using just the class
name.
FIGURE 6-8
InputStream is = this.getResources().openRawResource(R.raw.textfile);
BufferedReader br = new BufferedReader(new InputStreamReader(is)); String
str = null;
try {
while ((str = br.readLine()) != null) {Toast.makeText(getBaseContext(),
str, Toast.LENGTH_SHORT).show();
}
is.close();
br.close();
} catch (IOException e) {e.printStackTrace();
}
loadBtn.setOnClickListener(new View.OnClickListener() {
❘
The resource ID of the resource stored in the res/raw folder is named after its
filename without its extension. For example, if the text file is textfile.txt, then its
resource ID is R.raw.textfile.
FIGURE 6-10
DatabaseHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db)
{
try {
db.execSQL(DATABASE_CREATE);
} catch (SQLException e) {e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
Log.w(TAG, “Upgrading database from version “ + oldVersion + “ to “
+ newVersion + “, which will destroy all old data”); db.execSQL(“DROP
TABLE IF EXISTS contacts”);
onCreate(db);
}
}
//---updates a contact---
public boolean updateContact(long rowId, String name, String email)
{
ContentValues args = new ContentValues(); args.put(KEY_NAME, name);
args.put(KEY_EMAIL, email);
return db.update(DATABASE_TABLE, args, KEY_ROWID + “=” + rowId, null) >
0;
}
}
❘
How It Works
You first defined several constants to contain the various fields for the table that
you are going to create in your database:
public static final String KEY_ROWID = “_id”; public static final String
KEY_NAME = “name”; public static final String KEY_EMAIL = “email”; private
static final String TAG = “DBAdapter”;
private static final String DATABASE_NAME = “MyDB”; private static final String
DATABASE_TABLE = “contacts”;private static final int DATABASE_VERSION = 1;
this.context = ctx;
DBHelper = new DatabaseHelper(context);
}
❘
@Override
public void onCreate(SQLiteDatabase db)
{
try {
db.execSQL(DATABASE_CREATE);
} catch (SQLException e) {e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
Log.w(TAG, “Upgrading database from version “ + oldVersion + “ to “
+ newVersion + “, which will destroy all old data”); db.execSQL(“DROP
TABLE IF EXISTS contacts”);
onCreate(db);
}
}
The onCreate() method creates a new database if the required database is not
present. The onUpgrade() method is called when the database needs to be upgraded.
This is achieved by checking the value defined in the DATABASE_VERSION
constant. For this implementation of the onUpgrade() method, you simply drop the
table and create it again.
You can then define the various methods for opening and closing the database, as
well as the methods for adding/editing/deleting rows in the table:
public class DBAdapter {
//...
//...
{
db = DBHelper.getWritableDatabase(); return this;
}
DBHelper.close();
}
//---updates a contact---
public boolean updateContact(long rowId, String name, String email)
{
ContentValues args = new ContentValues(); args.put(KEY_NAME, name);
args.put(KEY_EMAIL, email);
return db.update(DATABASE_TABLE, args, KEY_ROWID + “=” + rowId, null) >
0;
}
}
Notice that Android uses the Cursor class as a return value for queries. Think of the
Cursor as a pointer to the result set from a database query. Using Cursor enables
Android to more efficiently manage rows and columns as needed.
You use a ContentValues object to store key/value pairs. Its put() method enables you
to insert keys with values of different data types.
❘
Adding Contacts
The following Try It Out demonstrates how you can add a contact to the table.
//---add a contact---db.open();
long id = db.insertContact(“Wei-Meng Lee”, “[email protected]”); id =
db.insertContact(“Mary Jackson”, “[email protected]”);
db.close();
}
}
2. Press F11 to debug the application on the Android Emulator.
How It Works
In this example, you first created an instance of the DBAdapter class:
DBAdapter db = new DBAdapter(this);
The insertContact() method returns the ID of the inserted row. If an error occurs during the
operation, it returns -1.
If you examine the file system of the Android device/emulator using DDMS, you can see that the
MyDB database is created under the databases folder (see
Figure 6-11). FIGURE 6-11
.java file:
package net.learn2develop.Databases;
import android.database.Cursor;
/*
//---add a contact---db.open();
long id = db.insertContact(“Wei-Meng Lee”, “[email protected]”); id =
db.insertContact(“Mary Jackson”, “[email protected]”);
db.close();
*/
FIGURE 6-12
How It Works
The getAllContacts() method of the DBAdapter class retrieves all the contacts stored in the database. The
result is returned as a Cursor object. To display all the contacts, you first need to call the
moveToFirst()method of the Cursor object. If it succeeds (which means at least one row is available),
display the details of the contact using the DisplayContact() method. To move to the next contact, call
the moveToNext() method of the Cursor object.
/*
//---add a contact---
//...
*/
/*
❘
//---get a contact---db.open();
Cursor c = db.getContact(2);if (c.moveToFirst())
DisplayContact(c);else
Toast.makeText(this, “No contact found”, Toast.LENGTH_LONG).show();db.close();
}
2. Press F11 to debug the application on the Android Emulator. The details of the second contact will
be displayed using the Toast class.
How It Works
The getContact() method of the DBAdapter class retrieves a single contact using its ID. You passed
in the ID of the contact; in this case, you passed in an ID of 2 to indicate that you want to retrieve
the second contact:
Cursor c = db.getContact(2);
The result is returned as a Cursor object. If a row is returned, you display the details of the contact
using the DisplayContact() method; otherwise, you display a message using the Toast class.
Updating a Contact
To update a particular contact, call the updateContact() method in the DBAdapter class by passing the
ID of the contact you want to update, as the following Try It Out shows.
/*
//---add a contact---
//...
*/
/*
//---get all contacts---
//...
*/
/*
//...
❘
*/
//---update contact---db.open();
if (db.updateContact(1, “Wei-Meng Lee”, “[email protected]”)) Toast.makeText(this, “Update
successful.”, Toast.LENGTH_LONG).show();
else
Toast.makeText(this, “Update failed.”, Toast.LENGTH_LONG).show();db.close();
}
2. Press F11 to debug the application on the Android Emulator. A message will be displayed if the
update is successful.
How It Works
The updateContact() method in the DBAdapter class updates a contact’s details by using the ID of the
contact you want to update. It returns a Boolean value, indicating whether the update was
successful.
Deleting a Contact
To delete a contact, use the deleteContact() method in the DBAdapter class by passing the ID of the
contact you want to update, as the following Try It Out shows.
/*
//---add a contact---
//...
*/
/*
//---get all contacts---
//...
*/
/*
//---get a contact---
//...
*/
/*
//---update contact---
❘
//...
*/
//---delete a contact---db.open();
if (db.deleteContact(1))
Toast.makeText(this, “Delete successful.”, Toast.LENGTH_LONG).show();else
Toast.makeText(this, “Delete failed.”, Toast.LENGTH_LONG).show();db.close();
}
2. Press F11 to debug the application on the Android Emulator. A message will be displayed if the
deletion was successful.
How It Works
The deleteContact() method in the DBAdapter class deletes a contact using the ID of the contact you
want to update. It returns a Boolean value, indicating whether the deletion was successful.
❘
A content provider behaves very much like a database — you can query it, edit its
content, as well as add or delete its content. However, unlike a database, a content
provider can use different ways to store its data. The data can be stored in a database, in
files, or even over a network.
Android ships with many useful content providers, including the following:
❘
Content URI
content:// – Mandatory part of the URI as it represents that the given URI is a Content
URI.
authority – Signifies the name of the content provider like contacts, browser, etc. This part
must be unique for every content provider.
optionalPath – Specifies the type of data provided by the content provider. It is essential
as this part helps content providers to support different types of data that are not related to
each other like audio and video files.
optionalID – It is a numeric value that is used when there is a need to access a particular
record.
Four fundamental operations are possible in Content Provider namely Create, Read,
Update, and Delete. These operations are often termed as CRUD operations.
After receiving a request, ContentProvider process it and returns the desired result. Below
is a diagram to represent these processes in pictorial form.
This involves number of simple steps to create your own content provider.
First of all you need to create a Content Provider class that extends the
ContentProviderbaseclass.
Second, you need to define your content provider URI address which will be used to access
the content.
Next you will need to create your own database to keep the content. Usually, Android uses
SQLite database and framework needs to override onCreate() method which will use
SQLite Open Helper method to create or open the provider's database. When your
application is launched, the onCreate() handler of each of its Content Providers is called
on the main application thread.
Next you will have to implement Content Provider queries to perform different database
specific operations.
Finally register your Content Provider in your activity file using <provider> tag.
Following are the six abstract methods and their description which are essential to
override as the part of ContenProvider class:
❘
Abstract MethodDescription
A method that accepts arguments and fetches the data from the
query()
desired table. Data is retired as a cursor object.
To insert a new row in the database of the content provider.
insert()
It returns the content URI of the inserted row.
This method is used to update the fields of an existing row.
update()
It returns the number of rows updated.
This method is used to delete the existing rows.
delete()
It returns the number of rows deleted.
This method returns the Multipurpose Internet Mail Extension(MIME)
getType()
type of data to the given Content URI.
As the content provider is created, the android system calls
onCreate()
this method immediately to initialise the provider.
❘
Accessing a provider
An application accesses the data from a content provider with a ContentResolver client
object. This object has methods that call identically-named methods in the provider
object, an instance of one of the concrete subclasses of ContentProvider. The
ContentResolver methods provide the basic "CRUD" (create, retrieve, update, and delete)
functions of persistent storage.
The ContentResolver object in the client application's process and the ContentProvider
object in the application that owns the provider automatically handle inter-process
communication. ContentProvider also acts as an abstraction layer between its repository
of data and the external appearance of data as tables.
Note: To access a provider, your application usually has to request specific permissions
in its manifest file. This is described in more detail in the section Content Provider
Permissions
For example, to get a list of the words and their locales from the User Dictionary
Provider, you call ContentResolver.query(). The query() method calls the
ContentProvider.query() method defined by the User Dictionary Provider. The following
lines of code show a ContentResolver.query() call:
query() SELECT
Notes
argument keyword/parameter
Uri maps to the table in the
Uri FROM table_name
provider named table_name.
projection is an array of
columns that should be
projection col,col,col,...
included for each row
retrieved.
selection specifies the criteria
selection WHERE col = value
for selecting rows.
(No exact
equivalent. Selection
selectionArgs arguments replace ?
placeholders in the
selection clause.)
sortOrder specifies the order in
ORDER BY
sortOrder which rows appear in the
col,col,...
returned Cursor.