What I want to do, have a [BroadcastReceiver] that listens to any incoming SMS, that when the incoming SMS has a certain keyword, it will pass over to device admin and invoke the required policy.
I have created a BroadcastReceiver that listen to SMS and pop a toast, and it works fine. Then I created a device admin app following this guide: http://marakana.com/s/post/1291/android_device_policy_administration_tutorial, and its working as it should. But when I include that SMS receiver together with device admin, it crashes whenever an sms comes in. Also, I'm unable to pass the string that I want to use from the SMS receiver to my device admin app in order to invoke the required policy.
This is my device admin
public class AppPolicyActivity extends Activity implements OnCheckedChangeListener {
static final int ACTIVATION_REQUEST = 47;
DevicePolicyManager mDPM;
ComponentName appPolicy;
ToggleButton toggleBtn;
#Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity_layout);
toggleBtn = (ToggleButton) findViewById(R.id.toggleBtn);
toggleBtn.setOnCheckedChangeListener(this);
mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
appPolicy = new ComponentName(this, AppPolicyReceiver.class);
// The commented code below made my app crash during installation
/* Intent intent;
try {
if (getIntent() != null) {
intent = getIntent();
String keyword = intent.getStringExtra("keyword");
if (keyword.equals("LOCK")) {
receivedSMS(keyword);
} else if (keyword.equals("WIPE")) {
receivedSMS(keyword);
}
keyword = "";
intent = null;
finish();
}
} catch (Exception e) {} */
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// TODO Auto-generated method stub
if (isChecked) {
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, appPolicy);
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "Activate the application to use its features");
startActivityForResult(intent, ACTIVATION_REQUEST);
} else {
mDPM.removeActiveAdmin(appPolicy);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
switch (requestCode) {
case ACTIVATION_REQUEST:
if (resultCode == Activity.RESULT_OK) {
toggleBtn.setChecked(true);
} else {
toggleBtn.setChecked(false);
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
public void receivedSMS(String keyword) {
// Lock
if (keyword.equals("LOCK")) {
mDPM.lockNow();
}
// Wipe/delete
else if (keyword.equals("WIPE")) {
mDPM.wipeData(ACTIVATION_REQUEST);
}
}
}
This is the device admin receiver class
public class AppPolicyReceiver extends DeviceAdminReceiver {
#Override
public void onDisabled(Context context, Intent intent) {
// TODO Auto-generated method stub
Toast.makeText(context, R.string.disabletext, Toast.LENGTH_SHORT).show();
super.onDisabled(context, intent);
}
#Override
public void onEnabled(Context context, Intent intent) {
// TODO Auto-generated method stub
Toast.makeText(context, R.string.enabletext, Toast.LENGTH_SHORT).show();
super.onEnabled(context, intent);
}
}
this is my manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fyp.mobilesecurity"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<application android:label="#string/app_name"
android:icon="#drawable/ic_launcher"
android:theme="#style/AppTheme">
<activity android:name="AppPolicyActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- appPolicyReceiver -->
<receiver android:permission="android.permission.BIND_DEVICE_ADMIN" android:name="AppPolicyReceiver">
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
<action android:name="android.app.action.DEVICE_ADMIN_DISABLED"/>
</intent-filter>
<meta-data android:resource="#xml/app_admin"
android:name="android.app.device_admin"/>
</receiver>
<!-- SMSReceiver -->
<receiver android:enabled="true" android:name="SMSReceiver" android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
and this is from log, as you can see my receiver is not even started.
11-17 21:06:52.300: E/AndroidRuntime(375): java.lang.RuntimeException: Unable to start receiver com.fyp.mobilesecurity.SMSReceiver: java.lang.NullPointerException
I just realize that its returning an exception, so I enclosed my receiver in try/catch block, and now its working fine.
You'll need to see what's causing the NullPointerException in SMSReceiver. The stacktrace will have the line number.
Related
How can I make service listen for media button (headset-hook) in android device .
I try to do that using this code but did not work.
Can you help me.
Service code:
public class RLservice extends IntentService {
private Handler handler;
public RLservice() {
super("my service");
// TODO Auto-generated constructor stub
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
handler = new Handler();
return super.onStartCommand(intent, flags, startId);
}
#Override
protected void onHandleIntent(Intent arg0) {
String intentAction = arg0.getAction();
if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
KeyEvent event = (KeyEvent) arg0
.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event == null) {
return;
}
int keycode = event.getKeyCode();
int action = event.getAction();
long eventtime = event.getEventTime();
if (keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|| keycode == KeyEvent.KEYCODE_HEADSETHOOK) {
if (action == KeyEvent.ACTION_DOWN) {
// Start your app here!
handler.post(new Runnable() {
public void run() {
// TODO Auto-generated method stub
Toast.makeText(getApplicationContext(), "Welcome",
Toast.LENGTH_LONG).show();
}
});
}
}
}
}
}
This is reciver code:
public class RLreciver extends BroadcastReceiver
{
#Override
public void onReceive(Context arg0, Intent arg1) {
// TODO Auto-generated method stub
Intent i=new Intent(RLservice.class.getName());
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
arg0.startService(i);
}
}
This is manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ypu.RLservice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<receiver android:name=".RLreciver" >
<intent-filter android:priority="100000" >
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<service
android:name=".RLservice"
android:process=":remote" >
<intent-filter>
<action android:name="com.ypu.RLservice.RLservice" />
</intent-filter>
</service>
</application>
</manifest>
Thank you in advance.
Your service is expecting an Intent with the ACTION_MEDIA_BUTTON Intent action. You are not starting the service using such an Intent.
Change:
Intent i=new Intent(RLservice.class.getName());
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
arg0.startService(i);
to:
arg1.setClass(this, RLservice.class);
arg0.startService(arg1);
This will effectively "redirect" the broadcast as a command to your service, with the action and extras intact.
Also:
Get rid of android:process=":remote", as it is not necessary here and is user-hostile
Get rid of your <intent-filter> in your <service>, unless you intend for a million other apps to be calling this service
I am trying to use BroadcastReceiver but it is not working, please help me to solve this problem.
MyReceiver.java
package com.example.broadcast_receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class MyReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.i("[BroadcastReceiver]", "MyReceiver");
if(intent.getAction().equals(Intent.ACTION_SCREEN_ON)){
Log.i("[BroadcastReceiver]", "Screen ON");
}
else if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)){
Log.i("[BroadcastReceiver]", "Screen OFF");
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcast_receiver"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="16" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<receiver android:name=".MyReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.SCREEN_ON"/>
<action android:name="android.intent.action.SCREEN_OFF"/>
</intent-filter>
</receiver>
<activity
android:name="com.example.broadcast_receiver.MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
BroadcastReceiver not working and not making any log, please help me to solve this problem.
Hey try using dynamic calling of broadcast,I tried this it will surly work...
public class MainActivity extends Activity {
//Create broadcast object
BroadcastReceiver mybroadcast = new BroadcastReceiver() {
//When Event is published, onReceive method is called
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.i("[BroadcastReceiver]", "MyReceiver");
if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
Log.i("[BroadcastReceiver]", "Screen ON");
}
else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Log.i("[BroadcastReceiver]", "Screen OFF");
}
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
registerReceiver(mybroadcast, new IntentFilter(Intent.ACTION_SCREEN_ON));
registerReceiver(mybroadcast, new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
}
If you want this receiver to be called by the system, you would need to export it. You set exported = "false", change this to true or remove exported entirely and this will start working. Normally this would be insecure, but as both SCREEN_ON and SCREEN_OFF are protected-broadcasts, and you verify the actions, only more trusted system code can send them too you, so it's pretty safe.
Sadly this wont work in this case as the intents broadcast have the following flags:
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
Can you try with getting battery value:
public class Broadcast extends Activity {
//Create broadcast object
BroadcastReceiver mybroadcast = new BroadcastReceiver() {
//When Event is published, onReceive method is called
#Override
public void onReceive(Context context, Intent intent) {
//Get battery percentage
int batterylevel = intent.getIntExtra("level", 0);
//get progressbar
ProgressBar myprogressbar = (ProgressBar) findViewById(R.id.progressbar);
myprogressbar.setProgress(batterylevel);
TextView tv = (TextView) findViewById(R.id.textfield);
//Set TextView with text
tv.setText("Battery Level: " + Integer.toString(batterylevel) + "%");
}
});
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_broadcast);
registerReceiver(mybroadcast, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
}
I am trying to make an android app that must do some database activities like storing phone number and name etc in the SQLite DB . This all Data Base stuff is done as a service. The service must start as soon as the call comes and when the call ends the stored details must be showed to user as soon as the call ends. For this purpose I am using Broad Cast Receiver. I have also provide the following codes which I have used in my app.
MyServices.java
public class MyServices extends Service {
TelephonyManager Tel;
MyPhoneStateListener MyListener;
RB_SIGNAL_STRENGTH signalobj = new RB_SIGNAL_STRENGTH();
RB_DatabaseHandler db = new RB_DatabaseHandler(getApplicationContext());
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
isMyServiceRunning();
MyListener = new MyPhoneStateListener();
Tel = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
Tel.listen(MyListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
super.onCreate();
Log.i("Serv","Service Started");
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy() {
// TODO Auto-generated method stub
db.close_DB();
Log.i("Serv","Service Stopped");
super.onDestroy();
}
MyReceiver.java
public class MyPhoneReceiver extends BroadcastReceiver {
String state=null;
#Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
if (state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_OFFHOOK))
{
try{
Intent i = new Intent(context,MyServices.class);
Log.i("Recv", "In Try");
context.startService(i);
}
catch(Exception e)
{
Log.d("Recv", "Service not starting");
}
}//End if offhook
if(state.equals(TelephonyManager.EXTRA_STATE_IDLE))
{
Log.i("Recv","CALL ENDED");
try
{
Intent i = new Intent(context,EndActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
catch(Exception e){
Log.d("Recv", "Activity not starting");
}
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testapp"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="9" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" >
</uses-permission>
<uses-permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES" />
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity android:name="StartActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="EndActivity" >
</activity>
<receiver android:name=".MyReceiver" >
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" >
</action>
</intent-filter>
</receiver>
<service
android:name="MyServices"
></service>
</application>
Your class name is MyPhoneReceiver but in your Manifest you are using MyReceiver, these two names must match exactly.
Addition
I just noticed that you are trying to instantiate your database before the Service has a valid Context. This will probably throw an exception in MyServices:
RB_DatabaseHandler db = new RB_DatabaseHandler(getApplicationContext());
You can to declare db as a field variable but leave it null:
RB_DatabaseHandler db;
And inside a method like onStartCommand() initialize it:
db = new RB_DatabaseHandler(getApplicationContext());
Lastly, calling fundamental methods like onCreate() out of order usually creates problems, this is not recommended in onStartCommand():
super.onCreate();
because state variable is null
public class MyPhoneReceiver extends BroadcastReceiver {
String state=null;
#Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
//your code here...
I'm trying to build a really simple app to wipe all the user data off a ICS device.
I've tried to create the app using the source code on http://developer.android.com/guide/topics/admin/device-admin.html
and
http://marakana.com/s/post/1291/android_device_policy_administration_tutorial
But I'm having an issue, no matter what I do, the broadcast receiver for prompting the user to allow admin does not appear!
Heres what I got so far if anyone can help with this issue.
Manifest:
<uses-sdk android:minSdkVersion="15" />
<application android:icon="#drawable/ic_launcher"
android:label="#string/app_name">
<activity android:name="com.CheckActivityService.CheckActivityServiceActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<receiver android:name=".mDeviceAdminReceiver"
android:label="device_admin" android:permission="android.permission.BIND_DEVICE_ADMIN">
<meta-data android:name="android.app.device_admin"
android:resource="#xml/policies" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
</manifest>
Activity:
public class CheckActivityServiceActivity extends Activity implements OnCheckedChangeListener{
/** Called when the activity is first created. */
static final int ACTIVATION_REQUEST = 1; // identifies our request id
DevicePolicyManager devicePolicyManager;
ComponentName deviceAdmin;
ToggleButton toggleButton;
static final String TAG = "DevicePolicyActivity";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
toggleButton = (ToggleButton) super
.findViewById(R.id.toggle_device_admin);
toggleButton.setOnCheckedChangeListener(this);
Button btn = (Button) findViewById(R.id.wipeDataBtn);
btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
wipeData();
}
});
devicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
deviceAdmin = new ComponentName(CheckActivityServiceActivity.this, mDeviceAdminReceiver.class);
}
/**
* Called when the state of toggle button changes. In this case, we send an
* intent to activate the device policy administration.
*/
public void onCheckedChanged(CompoundButton button, boolean isChecked) {
if (isChecked) {
// Launch the activity to have the user enable our admin.
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, deviceAdmin);
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "My Boss told me to do this!!");
startActivityForResult(intent, ACTIVATION_REQUEST);
}
Log.d(TAG, "onCheckedChanged to: " + isChecked);
}
public void wipeData(){
devicePolicyManager.wipeData(ACTIVATION_REQUEST);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case ACTIVATION_REQUEST:
if (resultCode == Activity.RESULT_OK) {
Log.i(TAG, "Administration enabled!");
toggleButton.setChecked(true);
} else {
Log.i(TAG, "Administration enable FAILED!");
toggleButton.setChecked(false);
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
}
Reciever:
public class mDeviceAdminReceiver extends DeviceAdminReceiver {
static final String TAG = "DeviceAdminReceiver";
void showToast(Context context, String msg) {
String status = "TEST";
Toast.makeText(context, status, Toast.LENGTH_SHORT).show();
}
/** Called when this application is approved to be a device administrator. */
#Override
public void onEnabled(Context context, Intent intent) {
super.onEnabled(context, intent);
Toast.makeText(context, "Admin Enabeled",
Toast.LENGTH_LONG).show();
Log.d(TAG, "onEnabled");
}
/** Called when this application is no longer the device administrator. */
#Override
public void onDisabled(Context context, Intent intent) {
super.onDisabled(context, intent);
Toast.makeText(context, "Admin Disabled",
Toast.LENGTH_LONG).show();
Log.d(TAG, "onDisabled");
}
#Override
public void onPasswordChanged(Context context, Intent intent) {
super.onPasswordChanged(context, intent);
Log.d(TAG, "onPasswordChanged");
}
#Override
public void onPasswordFailed(Context context, Intent intent) {
super.onPasswordFailed(context, intent);
Log.d(TAG, "onPasswordFailed");
}
#Override
public void onPasswordSucceeded(Context context, Intent intent) {
super.onPasswordSucceeded(context, intent);
Log.d(TAG, "onPasswordSucceeded");
}
}
If anyone can help me to get this to work, as I really can't figure out why the broadcast receiver isn't firing.
Figured out problem, kind of stupid of me.
needed to put the receiver in the element.
How to capture incoming MESSAGE type whether it is Text or MMS or something else in Android programmatic ally?
earlier i came here for solution but didn't get.
now i have solved it and decided to post answer myself.
if you want to know message type is Text or MMS?
create an Activity where we will start a Service
public class ListenSMSActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//start the service to listen
startService(new Intent(getBaseContext(),ListenSMSService.class));
}}
create a service where we will use content Observer
public class ListenSMSService extends Service{
//Context globalContext;
ContentResolver contentResolver;
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onCreate() {
registerObserver();
}
public void registerObserver() {
contentResolver = getContentResolver();
contentResolver.registerContentObserver(Uri.parse
("content://mms-sms/conversations/"), true,
new MyObserver(new Handler()));
Log.v("Debug", " in registerObserver method.........");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
class MyObserver extends ContentObserver
{
public MyObserver(Handler handler) {
super(handler);
// TODO Auto-generated constructor stub
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.v("Current Locaion", "in onChange method....");
final String[] projection = new String[]{"_id", "ct_t"};
Uri uri = Uri.parse("content://mms-sms/conversations/");
Cursor query = contentResolver.query(uri, projection, null, null, null);
query.moveToFirst();
String string = query.getString(query.getColumnIndex("ct_t"));
if ("application/vnd.wap.multipart.related".equals(string))
{
// it's MMS
Log.v("Debug", "it's MMS");
}
else
{
// it's SMS
Log.v("Debug", "it's SMS");
}
}
}
}
and finally add permissions and make entry for service in manifest file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rdc"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<uses-permission android:name="android.permission.SEND_SMS"></uses-permission>
<uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
<uses-permission android:name="android.permission.READ_SMS"></uses-permission>
<application android:icon="#drawable/icon" android:label="#string/app_name">
<activity android:name="com.rdc.ListenSMSActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:enabled="true" android:name="com.rdc.ListenSMSService">
<intent-filter>
<action android:name="com.kns.ListenSMSService">
</action>
</intent-filter>
</service>
</application>
</manifest>
you will get the message type (sms/mms) in Logcat.