Merge "Add dumpsys facility and a history to TrustManagerService"
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index d8d3da1..47ce3b6 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -39,10 +38,15 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "TrustAgentWrapper";
 
-    private static final int MSG_ENABLE_TRUST = 1;
+    private static final int MSG_GRANT_TRUST = 1;
     private static final int MSG_REVOKE_TRUST = 2;
     private static final int MSG_TRUST_TIMEOUT = 3;
 
+    /**
+     * Long extra for {@link #MSG_GRANT_TRUST}
+     */
+    private static final String DATA_DURATION = "duration";
+
     private final TrustManagerService mTrustManagerService;
     private final int mUserId;
     private final Context mContext;
@@ -58,19 +62,32 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_ENABLE_TRUST:
+                case MSG_GRANT_TRUST:
                     mTrusted = true;
                     mMessage = (CharSequence) msg.obj;
                     boolean initiatedByUser = msg.arg1 != 0;
-                    // TODO: Handle handle user initiated trust changes.
+                    // TODO: Handle initiatedByUser.
+                    long durationMs = msg.getData().getLong(DATA_DURATION);
+                    if (durationMs > 0) {
+                        mHandler.removeMessages(MSG_TRUST_TIMEOUT);
+                        mHandler.sendEmptyMessageDelayed(MSG_TRUST_TIMEOUT, durationMs);
+                    }
+                    mTrustManagerService.mArchive.logGrantTrust(mUserId, mName,
+                            (mMessage != null ? mMessage.toString() : null),
+                            durationMs, initiatedByUser);
                     mTrustManagerService.updateTrust(mUserId);
                     break;
                 case MSG_TRUST_TIMEOUT:
                     if (DEBUG) Slog.v(TAG, "Trust timed out : " + mName.flattenToShortString());
+                    mTrustManagerService.mArchive.logTrustTimeout(mUserId, mName);
                     // Fall through.
                 case MSG_REVOKE_TRUST:
                     mTrusted = false;
                     mMessage = null;
+                    mHandler.removeMessages(MSG_TRUST_TIMEOUT);
+                    if (msg.what == MSG_REVOKE_TRUST) {
+                        mTrustManagerService.mArchive.logRevokeTrust(mUserId, mName);
+                    }
                     mTrustManagerService.updateTrust(mUserId);
                     break;
             }
@@ -84,12 +101,10 @@
             if (DEBUG) Slog.v(TAG, "enableTrust(" + userMessage + ", durationMs = " + durationMs
                         + ", initiatedByUser = " + initiatedByUser + ")");
 
-            mHandler.obtainMessage(MSG_ENABLE_TRUST, initiatedByUser ? 1 : 0, 0, userMessage)
-                    .sendToTarget();
-            if (durationMs > 0) {
-                mHandler.removeMessages(MSG_TRUST_TIMEOUT);
-                mHandler.sendEmptyMessageDelayed(MSG_TRUST_TIMEOUT, durationMs);
-            }
+            Message msg = mHandler.obtainMessage(
+                    MSG_GRANT_TRUST, initiatedByUser ? 1 : 0, 0, userMessage);
+            msg.getData().putLong(DATA_DURATION, durationMs);
+            msg.sendToTarget();
         }
 
         @Override
@@ -111,6 +126,7 @@
         public void onServiceDisconnected(ComponentName name) {
             if (DEBUG) Log.v(TAG, "TrustAgent disconnected : " + name.flattenToShortString());
             mTrustAgentService = null;
+            mTrustManagerService.mArchive.logAgentDied(mUserId, name);
             mHandler.sendEmptyMessage(MSG_REVOKE_TRUST);
         }
     };
@@ -165,4 +181,8 @@
         if (DEBUG) Log.v(TAG, "TrustAgent unbound : " + mName.flattenToShortString());
         mContext.unbindService(mConnection);
     }
+
+    public boolean isConnected() {
+        return mTrustAgentService != null;
+    }
 }
diff --git a/services/core/java/com/android/server/trust/TrustArchive.java b/services/core/java/com/android/server/trust/TrustArchive.java
new file mode 100644
index 0000000..aad156c
--- /dev/null
+++ b/services/core/java/com/android/server/trust/TrustArchive.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.trust;
+
+import android.content.ComponentName;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayDeque;
+import java.util.Iterator;
+
+/**
+ * An archive of trust events.
+ */
+public class TrustArchive {
+    private static final int TYPE_GRANT_TRUST = 0;
+    private static final int TYPE_REVOKE_TRUST = 1;
+    private static final int TYPE_TRUST_TIMEOUT = 2;
+    private static final int TYPE_AGENT_DIED = 3;
+
+    private static final int HISTORY_LIMIT = 200;
+
+    private static class Event {
+        final int type;
+        final int userId;
+        final ComponentName agent;
+        final long elapsedTimestamp;
+
+        // grantTrust
+        final String message;
+        final long duration;
+        final boolean userInitiated;
+
+        private Event(int type, int userId, ComponentName agent, String message,
+                long duration, boolean userInitiated) {
+            this.type = type;
+            this.userId = userId;
+            this.agent = agent;
+            this.elapsedTimestamp = SystemClock.elapsedRealtime();
+            this.message = message;
+            this.duration = duration;
+            this.userInitiated = userInitiated;
+        }
+    }
+
+    ArrayDeque<Event> mEvents = new ArrayDeque<Event>();
+
+    public void logGrantTrust(int userId, ComponentName agent, String message,
+            long duration, boolean userInitiated) {
+        addEvent(new Event(TYPE_GRANT_TRUST, userId, agent, message, duration,
+                userInitiated));
+    }
+
+    public void logRevokeTrust(int userId, ComponentName agent) {
+        addEvent(new Event(TYPE_REVOKE_TRUST, userId, agent, null, 0, false));
+    }
+
+    public void logTrustTimeout(int userId, ComponentName agent) {
+        addEvent(new Event(TYPE_TRUST_TIMEOUT, userId, agent, null, 0, false));
+    }
+
+    public void logAgentDied(int userId, ComponentName agent) {
+        addEvent(new Event(TYPE_AGENT_DIED, userId, agent, null, 0, false));
+    }
+
+    private void addEvent(Event e) {
+        if (mEvents.size() >= HISTORY_LIMIT) {
+            mEvents.removeFirst();
+        }
+        mEvents.addLast(e);
+    }
+
+    public void dump(PrintWriter writer, int limit, int userId, String linePrefix,
+            boolean duplicateSimpleNames) {
+        int count = 0;
+        Iterator<Event> iter = mEvents.descendingIterator();
+        while (iter.hasNext() && count < limit) {
+            Event ev = iter.next();
+            if (userId != UserHandle.USER_ALL && userId != ev.userId) {
+                continue;
+            }
+
+            writer.print(linePrefix);
+            writer.printf("#%-2d %s %s: ", count, formatElapsed(ev.elapsedTimestamp),
+                    dumpType(ev.type));
+            if (userId == UserHandle.USER_ALL) {
+                writer.print("user="); writer.print(ev.userId); writer.print(", ");
+            }
+            writer.print("agent=");
+            if (duplicateSimpleNames) {
+                writer.print(ev.agent.flattenToShortString());
+            } else {
+                writer.print(getSimpleName(ev.agent));
+            }
+            switch (ev.type) {
+                case TYPE_GRANT_TRUST:
+                    writer.printf(", message=\"%s\", duration=%s",
+                            ev.message, formatDuration(ev.duration));
+                    break;
+                default:
+            }
+            writer.println();
+            count++;
+        }
+    }
+
+    private static String formatDuration(long duration) {
+        StringBuilder sb = new StringBuilder();
+        TimeUtils.formatDuration(duration, sb);
+        return sb.toString();
+    }
+
+    private static String formatElapsed(long elapsed) {
+        long delta = elapsed - SystemClock.elapsedRealtime();
+        long wallTime = delta + System.currentTimeMillis();
+        return TimeUtils.logTimeOfDay(wallTime);
+    }
+
+    /* package */ static String getSimpleName(ComponentName cn) {
+        String name = cn.getClassName();
+        int idx = name.lastIndexOf('.');
+        if (idx < name.length() && idx >= 0) {
+            return name.substring(idx + 1);
+        } else {
+            return name;
+        }
+    }
+
+    private String dumpType(int type) {
+        switch (type) {
+            case TYPE_GRANT_TRUST:
+                return "GrantTrust";
+            case TYPE_REVOKE_TRUST:
+                return "RevokeTrust";
+            case TYPE_TRUST_TIMEOUT:
+                return "TrustTimeout";
+            case TYPE_AGENT_DIED:
+                return "AgentDied";
+            default:
+                return "Unknown(" + type + ")";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index efaa91b..986cdc1 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -24,6 +24,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.Manifest;
+import android.app.ActivityManagerNative;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustListener;
 import android.app.trust.ITrustManager;
@@ -52,7 +53,9 @@
 import android.util.SparseBooleanArray;
 import android.util.Xml;
 
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -87,6 +90,7 @@
     private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<ITrustListener>();
     private final DevicePolicyReceiver mDevicePolicyReceiver = new DevicePolicyReceiver();
     private final SparseBooleanArray mUserHasAuthenticatedSinceBoot = new SparseBooleanArray();
+    /* package */ final TrustArchive mArchive = new TrustArchive();
     private final Context mContext;
 
     private UserManager mUserManager;
@@ -367,6 +371,61 @@
             mContext.enforceCallingPermission(Manifest.permission.TRUST_LISTENER,
                     "register trust listener");
         }
+
+        @Override
+        protected void dump(FileDescriptor fd, final PrintWriter fout, String[] args) {
+            mContext.enforceCallingPermission(Manifest.permission.DUMP,
+                    "dumping TrustManagerService");
+            final UserInfo currentUser;
+            final List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
+            try {
+                currentUser = ActivityManagerNative.getDefault().getCurrentUser();
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+            mHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    fout.println("Trust manager state:");
+                    for (UserInfo user : userInfos) {
+                        dumpUser(fout, user, user.id == currentUser.id);
+                    }
+                }
+            }, 1500);
+        }
+
+        private void dumpUser(PrintWriter fout, UserInfo user, boolean isCurrent) {
+            fout.printf(" User \"%s\" (id=%d, flags=%#x)",
+                    user.name, user.id, user.flags);
+            if (isCurrent) {
+                fout.print(" (current)");
+            }
+            fout.print(": trusted=" + dumpBool(aggregateIsTrusted(user.id)));
+            fout.println();
+            fout.println("   Enabled agents:");
+            boolean duplicateSimpleNames = false;
+            ArraySet<String> simpleNames = new ArraySet<String>();
+            for (AgentInfo info : mActiveAgents) {
+                if (info.userId != user.id) { continue; }
+                boolean trusted = info.agent.isTrusted();
+                fout.print("    "); fout.println(info.component.flattenToShortString());
+                fout.print("     connected=" + dumpBool(info.agent.isConnected()));
+                fout.println(", trusted=" + dumpBool(trusted));
+                if (trusted) {
+                    fout.println("      message=\"" + info.agent.getMessage() + "\"");
+                }
+                if (!simpleNames.add(TrustArchive.getSimpleName(info.component))) {
+                    duplicateSimpleNames = true;
+                }
+            }
+            fout.println("   Events:");
+            mArchive.dump(fout, 50, user.id, "    " /* linePrefix */, duplicateSimpleNames);
+            fout.println();
+        }
+
+        private String dumpBool(boolean b) {
+            return b ? "1" : "0";
+        }
     };
 
     private final Handler mHandler = new Handler() {