/*
 * Copyright (C) 2018 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.cts;

import com.android.server.am.ActiveInstrumentationProto;
import com.android.server.am.ActiveServicesProto;
import com.android.server.am.ActiveServicesProto.ServicesByUser;
import com.android.server.am.ActivityManagerServiceDumpBroadcastsProto;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto.LruProcesses;
import com.android.server.am.ActivityManagerServiceDumpServicesProto;
import com.android.server.am.AppErrorsProto;
import com.android.server.am.AppTimeTrackerProto;
import com.android.server.am.BroadcastQueueProto;
import com.android.server.am.BroadcastQueueProto.BroadcastSummary;
import com.android.server.am.BroadcastRecordProto;
import com.android.server.am.ConnectionRecordProto;
import com.android.server.am.GrantUriProto;
import com.android.server.am.ImportanceTokenProto;
import com.android.server.am.NeededUriGrantsProto;
import com.android.server.am.ProcessRecordProto;
import com.android.server.am.ServiceRecordProto;
import com.android.server.am.UidRecordProto;
import com.android.server.am.UriPermissionOwnerProto;
import com.android.server.am.VrControllerProto;

/**
 * Test to check that the activity manager service properly outputs its dump state.
 *
 * make -j32 CtsIncidentHostTestCases
 * cts-tradefed run singleCommand cts-dev -d --module CtsIncidentHostTestCases
 */
public class ActivityManagerIncidentTest extends ProtoDumpTestCase {

    private static final String TEST_BROADCAST = "com.android.mybroadcast";
    private static final String SYSTEM_PROC = "system";
    private static final int SYSTEM_UID = 1000;

    /**
     * Tests activity manager dumps broadcasts.
     */
    public void testDumpBroadcasts() throws Exception {
        getDevice().executeShellCommand("am broadcast -a " + TEST_BROADCAST);
        getDevice().executeShellCommand("am wait-for-broadcast-barrier");
        final ActivityManagerServiceDumpBroadcastsProto dump = getDump(
                ActivityManagerServiceDumpBroadcastsProto.parser(),
                "dumpsys activity --proto broadcasts");

        assertTrue(dump.getReceiverListCount() > 0);
        assertTrue(dump.getBroadcastQueueCount() > 0);
        assertTrue(dump.getStickyBroadcastsCount() > 0);

        boolean found = false;
mybroadcast:
        for (BroadcastQueueProto queue : dump.getBroadcastQueueList()) {
            for (BroadcastRecordProto record : queue.getHistoricalBroadcastsList()) {
                if (record.getIntentAction().equals(TEST_BROADCAST)) {
                    found = true;
                    break mybroadcast;
                }
            }
            for (BroadcastSummary summary : queue.getHistoricalBroadcastsSummaryList()) {
                if (summary.getIntent().getAction().equals(TEST_BROADCAST)) {
                    found = true;
                    break mybroadcast;
                }
            }
        }
        assertTrue(found);
        ActivityManagerServiceDumpBroadcastsProto.MainHandler mainHandler = dump.getHandler();
        assertTrue(mainHandler.getHandler().contains(
            "com.android.server.am.ActivityManagerService"));
    }

    /**
     * Tests activity manager dumps services.
     */
    public void testDumpServices() throws Exception {
        final ActivityManagerServiceDumpServicesProto dump = getDump(
                ActivityManagerServiceDumpServicesProto.parser(),
                "dumpsys activity --proto service");
        ActiveServicesProto activeServices = dump.getActiveServices();
        assertTrue(activeServices.getServicesByUsersCount() > 0);

        for (ServicesByUser perUserServices : activeServices.getServicesByUsersList()) {
            assertTrue(perUserServices.getServiceRecordsCount() >= 0);
            for (ServiceRecordProto service : perUserServices.getServiceRecordsList()) {
                assertFalse(service.getShortName().isEmpty());
                assertFalse(service.getPackageName().isEmpty());
                assertFalse(service.getProcessName().isEmpty());
                assertFalse(service.getAppinfo().getBaseDir().isEmpty());
                assertFalse(service.getAppinfo().getDataDir().isEmpty());
            }
        }

        verifyActivityManagerServiceDumpServicesProto(dump, PRIVACY_NONE);
    }

    static void verifyActivityManagerServiceDumpServicesProto(ActivityManagerServiceDumpServicesProto dump, final int filterLevel) throws Exception {
        for (ServicesByUser sbu : dump.getActiveServices().getServicesByUsersList()) {
            for (ServiceRecordProto srp : sbu.getServiceRecordsList()) {
                verifyServiceRecordProto(srp, filterLevel);
            }
        }
    }

    private static void verifyServiceRecordProto(ServiceRecordProto srp, final int filterLevel) throws Exception {
        if (filterLevel == PRIVACY_AUTO) {
            assertTrue(srp.getAppinfo().getBaseDir().isEmpty());
            assertTrue(srp.getAppinfo().getResDir().isEmpty());
            assertTrue(srp.getAppinfo().getDataDir().isEmpty());
        } else {
            assertFalse(srp.getAppinfo().getBaseDir().isEmpty());
            assertFalse(srp.getAppinfo().getDataDir().isEmpty());
        }
        for (ServiceRecordProto.StartItem si : srp.getDeliveredStartsList()) {
            verifyServiceRecordProtoStartItem(si, filterLevel);
        }
        for (ServiceRecordProto.StartItem si : srp.getPendingStartsList()) {
            verifyServiceRecordProtoStartItem(si, filterLevel);
        }
        for (ConnectionRecordProto crp : srp.getConnectionsList()) {
            verifyConnectionRecordProto(crp, filterLevel);
        }
    }

    private static void verifyServiceRecordProtoStartItem(ServiceRecordProto.StartItem si, final int filterLevel) throws Exception {
        verifyNeededUriGrantsProto(si.getNeededGrants(), filterLevel);
        verifyUriPermissionOwnerProto(si.getUriPermissions(), filterLevel);
    }

    private static void verifyNeededUriGrantsProto(NeededUriGrantsProto nugp, final int filterLevel) throws Exception {
        for (GrantUriProto gup : nugp.getGrantsList()) {
            verifyGrantUriProto(gup, filterLevel);
        }
    }

    private static void verifyUriPermissionOwnerProto(UriPermissionOwnerProto upop, final int filterLevel) throws Exception {
        if (filterLevel == PRIVACY_AUTO) {
            assertTrue(upop.getOwner().isEmpty());
        }
        for (GrantUriProto gup : upop.getReadPermsList()) {
            verifyGrantUriProto(gup, filterLevel);
        }
        for (GrantUriProto gup : upop.getWritePermsList()) {
            verifyGrantUriProto(gup, filterLevel);
        }
    }

    private static void verifyGrantUriProto(GrantUriProto gup, final int filterLevel) throws Exception {
        if (filterLevel == PRIVACY_AUTO) {
            assertTrue(gup.getUri().isEmpty());
        }
    }

    private static void verifyConnectionRecordProto(ConnectionRecordProto crp, final int filterLevel) throws Exception {
        for (ConnectionRecordProto.Flag f : crp.getFlagsList()) {
            assertTrue(ConnectionRecordProto.Flag.getDescriptor().getValues().contains(f.getValueDescriptor()));
        }
    }

    /**
     * Tests activity manager dumps processes.
     */
    public void testDumpProcesses() throws Exception {
        final ActivityManagerServiceDumpProcessesProto dump = getDump(
                ActivityManagerServiceDumpProcessesProto.parser(),
                "dumpsys activity --proto processes");

        assertTrue(dump.getProcsCount() > 0);
        boolean procFound = false;
        for (ProcessRecordProto proc : dump.getProcsList()) {
            if (proc.getProcessName().equals(SYSTEM_PROC) && proc.getUid() == SYSTEM_UID) {
                procFound = true;
                break;
            }
        }
        assertTrue(procFound);

        assertTrue(dump.getActiveUidsCount() > 0);
        boolean uidFound = false;
        for (UidRecordProto uid : dump.getActiveUidsList()) {
            if (uid.getUid() == SYSTEM_UID) {
                uidFound = true;
                break;
            }
        }
        assertTrue(uidFound);

        LruProcesses lruProcs = dump.getLruProcs();
        assertTrue(lruProcs.getSize() == lruProcs.getListCount());
        assertTrue(dump.getUidObserversCount() > 0);
        assertTrue(dump.getAdjSeq() > 0);
        assertTrue(dump.getLruSeq() > 0);

        verifyActivityManagerServiceDumpProcessesProto(dump, PRIVACY_NONE);
    }

    static void verifyActivityManagerServiceDumpProcessesProto(ActivityManagerServiceDumpProcessesProto dump, final int filterLevel) throws Exception {
        for (ActiveInstrumentationProto aip : dump.getActiveInstrumentationsList()) {
            verifyActiveInstrumentationProto(aip, filterLevel);
        }
        for (UidRecordProto urp : dump.getActiveUidsList()) {
            verifyUidRecordProto(urp, filterLevel);
        }
        for (UidRecordProto urp : dump.getValidateUidsList()) {
            verifyUidRecordProto(urp, filterLevel);
        }
        for (ImportanceTokenProto itp : dump.getImportantProcsList()) {
            verifyImportanceTokenProto(itp, filterLevel);
        }
        verifyAppErrorsProto(dump.getAppErrors(), filterLevel);
        verifyVrControllerProto(dump.getVrController(), filterLevel);
        verifyAppTimeTrackerProto(dump.getCurrentTracker(), filterLevel);
        if (filterLevel == PRIVACY_AUTO) {
            assertTrue(dump.getMemWatchProcesses().getDump().getUri().isEmpty());
        }
    }

    private static void verifyActiveInstrumentationProto(ActiveInstrumentationProto aip, final int filterLevel) throws Exception {
    }

    private static void verifyUidRecordProto(UidRecordProto urp, final int filterLevel) throws Exception {
        for (UidRecordProto.Change c : urp.getLastReportedChangesList()) {
            assertTrue(UidRecordProto.Change.getDescriptor().getValues().contains(c.getValueDescriptor()));
        }
        assertTrue(urp.getNumProcs() >= 0);
    }

    private static void verifyImportanceTokenProto(ImportanceTokenProto itp, final int filterLevel) throws Exception {
        if (filterLevel == PRIVACY_AUTO) {
            // The entire message is tagged as EXPLICIT, so even the pid should be stripped out.
            assertTrue(itp.getPid() == 0);
            assertTrue(itp.getToken().isEmpty());
            assertTrue(itp.getReason().isEmpty());
        }
    }

    private static void verifyAppErrorsProto(AppErrorsProto aep, final int filterLevel) throws Exception {
        assertTrue(aep.getNowUptimeMs() >= 0);
        if (filterLevel == PRIVACY_AUTO) {
            for (AppErrorsProto.BadProcess bp : aep.getBadProcessesList()) {
                for (AppErrorsProto.BadProcess.Entry e : bp.getEntriesList()) {
                    assertTrue(e.getLongMsg().isEmpty());
                    assertTrue(e.getStack().isEmpty());
                }
            }
        }
    }

    private static void verifyVrControllerProto(VrControllerProto vcp, final int filterLevel) throws Exception {
        for (VrControllerProto.VrMode vm : vcp.getVrModeList()) {
            assertTrue(VrControllerProto.VrMode.getDescriptor().getValues().contains(vm.getValueDescriptor()));
        }
    }

    private static void verifyAppTimeTrackerProto(AppTimeTrackerProto attp, final int filterLevel) throws Exception {
        assertTrue(attp.getTotalDurationMs() >= 0);
        for (AppTimeTrackerProto.PackageTime pt : attp.getPackageTimesList()) {
            assertTrue(pt.getDurationMs() >= 0);
        }
    }
}
