com.github.michalbednarski.intentslab.ActivityMonitorActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.github.michalbednarski.intentslab.ActivityMonitorActivity.java

Source

/*
 * IntentsLab - Android app for playing with Intents and Binder IPC
 * Copyright (C) 2014 Micha Bednarski
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.github.michalbednarski.intentslab;

import android.app.IActivityController;
import android.app.ListActivity;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NotificationCompat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;
import com.github.michalbednarski.intentslab.editor.IntentEditorActivity;
import com.github.michalbednarski.intentslab.runas.IRemoteInterface;
import com.github.michalbednarski.intentslab.runas.RunAsManager;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by mb on 21.09.13.
 */
public class ActivityMonitorActivity extends ListActivity {

    private static ArrayList<Intent> sStagingIntents = null;
    private static final Handler sHandler = new Handler();
    private static final ArrayList<ActivityMonitorActivity> sStagingListViewers = new ArrayList<ActivityMonitorActivity>();

    private Intent[] mRecordedIntents;
    private ArrayAdapter<Intent> mAdapter;
    private boolean mIsInStagingListViewers = false;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            final Parcelable[] recordedIntentsRaw = savedInstanceState.getParcelableArray("recordedIntents");
            if (recordedIntentsRaw != null) {
                mRecordedIntents = new Intent[recordedIntentsRaw.length];
                System.arraycopy(recordedIntentsRaw, 0, mRecordedIntents, 0, recordedIntentsRaw.length);
            }
        } else if (sStagingIntents == null) {
            startRecording();
        }
        if (mRecordedIntents == null && sStagingIntents == null) {
            finish();
            return;
        }
        getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Object item = parent.getItemAtPosition(position);
                if (item instanceof Intent) {
                    startActivity(new Intent(ActivityMonitorActivity.this, IntentEditorActivity.class)
                            .putExtra(IntentEditorActivity.EXTRA_INTENT, (Intent) item));
                }
            }
        });
        chooseAndSetAdapter();
    }

    @Override
    protected void onDestroy() {
        addOrRemoveFromStagingListViewers(false);
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.activity_monitor, menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        boolean endedRecording = mRecordedIntents != null;
        menu.findItem(R.id.end_recording).setVisible(!endedRecording).setEnabled(!endedRecording);
        menu.findItem(R.id.save).setVisible(endedRecording).setEnabled(endedRecording);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.end_recording:
            endRecording();
            return true;
        // TODO: save
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mRecordedIntents != null) {
            outState.putParcelableArray("recordedIntents", mRecordedIntents);
        }
    }

    private void chooseAndSetAdapter() {
        if (mRecordedIntents != null) {
            mAdapter = new IntentArrayAdapter(this, mRecordedIntents);
            addOrRemoveFromStagingListViewers(false);
        } else {
            mAdapter = new IntentArrayAdapter(this, sStagingIntents);
            addOrRemoveFromStagingListViewers(true);
        }
        setListAdapter(mAdapter);
    }

    private void addOrRemoveFromStagingListViewers(boolean shouldBeInStagingListViewersList) {
        if (shouldBeInStagingListViewersList != mIsInStagingListViewers) {
            if (shouldBeInStagingListViewersList) {
                sStagingListViewers.add(this);
            } else {
                sStagingListViewers.remove(this);
            }
        }
        mIsInStagingListViewers = shouldBeInStagingListViewersList;
    }

    private void startRecording() {
        // Get remote interface
        IRemoteInterface mRemoteInterface = RunAsManager.getRemoteInterfaceForSystemDebuggingCommands();

        // Ensure it exists
        if (mRemoteInterface == null) {
            Toast.makeText(this, "No remote interface", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        // Set our watcher controller
        try {
            sStagingIntents = new ArrayList<Intent>();
            mRemoteInterface.setActivityController(new MyActivityController());
            startService(new Intent(this, ActivityRecorderService.class));
        } catch (RemoteException e) {
            Utils.toastException(this, e);
            finish();
        }
    }

    private void endRecording() {
        mRecordedIntents = sStagingIntents.toArray(new Intent[sStagingIntents.size()]);
        cancelRecording();
        stopService(new Intent(this, ActivityRecorderService.class));
        chooseAndSetAdapter();
        ActivityCompat.invalidateOptionsMenu(this);
    }

    private static void cancelRecording() {
        if (sStagingIntents == null) {
            return;
        }

        // Clear staging intents
        sStagingIntents = null;

        // Remove activity controller
        IRemoteInterface mRemoteInterface = RunAsManager.getRemoteInterfaceForSystemDebuggingCommands();
        try {
            if (mRemoteInterface != null) {
                mRemoteInterface.setActivityController(null);
            }
        } catch (RemoteException ignored) {
            // We cannot do anything to unregister, we have to keep controller which won't do anything
            // when sStagingIntents == null
        }
    }

    private static class IntentArrayAdapter extends ArrayAdapter<Intent> {

        public IntentArrayAdapter(Context context, List<Intent> objects) {
            super(context, 0, objects);
        }

        public IntentArrayAdapter(Context context, Intent[] objects) {
            super(context, 0, objects);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                final LayoutInflater inflater = (LayoutInflater) getContext()
                        .getSystemService(LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(android.R.layout.simple_list_item_2, parent, false);
            }
            final Intent intent = getItem(position);
            CharSequence text1;
            if (!Utils.stringEmptyOrNull(intent.getAction())) {
                text1 = intent.getAction();
            } else {
                final PackageManager pm = getContext().getPackageManager();
                try {
                    final ActivityInfo activityInfo = pm.getActivityInfo(intent.getComponent(), 0);
                    text1 = activityInfo.loadLabel(pm);
                } catch (Exception e) {
                    text1 = "?";
                }
            }

            ((TextView) convertView.findViewById(android.R.id.text1)).setText(text1);
            ((TextView) convertView.findViewById(android.R.id.text2))
                    .setText(intent.getComponent().flattenToShortString());
            return convertView;
        }
    }

    private static class MyActivityController extends IActivityController.Stub {

        @Override
        public boolean activityStarting(final Intent intent, String pkg) throws RemoteException {
            // Log
            sHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (sStagingIntents != null) {
                        sStagingIntents.add(intent);
                        for (ActivityMonitorActivity activity : sStagingListViewers) {
                            activity.mAdapter.notifyDataSetChanged();
                        }

                    }
                }
            });

            return true; // Allow starting activity
        }

        @Override
        public boolean activityResuming(String pkg) throws RemoteException {
            return true; // Allow resuming activity
        }

        @Override
        public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis,
                String stackTrace) throws RemoteException {
            return true; // Prompt user, normal system behavior
        }

        @Override
        public int appEarlyNotResponding(String processName, int pid, String annotation) throws RemoteException {
            return 0;
        }

        @Override
        public int appNotResponding(String processName, int pid, String processStats) throws RemoteException {
            return 0; // Prompt user, normal system behavior
        }

        @Override
        public int systemNotResponding(String msg) throws RemoteException {
            return -1; // Restart, normal system behavior
        }
    }

    public static class ActivityRecorderService extends Service {
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            startForeground(325,
                    new NotificationCompat.Builder(this).setOngoing(true).setSmallIcon(R.drawable.ic_launcher) // TODO
                            .setContentTitle("Recording activity intents...").setContentIntent(PendingIntent
                                    .getActivity(this, 0, new Intent(this, ActivityMonitorActivity.class), 0))
                            .build());
            return START_NOT_STICKY;
        }

        @Override
        public void onDestroy() {
            cancelRecording();
        }

        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
}