/*
 * 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.slice;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.content.ContentProvider;
import android.content.ContentResolver;
import android.net.Uri;
import android.net.Uri.Builder;
import android.os.FileUtils;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.util.Log;
import android.util.Xml.Encoding;

import androidx.test.filters.SmallTest;

import com.android.server.UiServiceTestCase;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class SlicePermissionManagerTest extends UiServiceTestCase {
    private static final String TAG = "SlicePerManTest";

    @Test
    public void testGrant() {
        File sliceDir = new File(mContext.getCacheDir(), "testGrantSlices");
        Log.v(TAG, "testGrant: slice permissions stored in " + sliceDir.getAbsolutePath());
        SlicePermissionManager permissions = new SlicePermissionManager(mContext,
                TestableLooper.get(this).getLooper(), sliceDir);
        Uri uri = new Builder().scheme(ContentResolver.SCHEME_CONTENT)
                .authority("authority")
                .path("something").build();

        permissions.grantSliceAccess("my.pkg", 0, "provider.pkg", 0, uri);

        assertTrue(permissions.hasPermission("my.pkg", 0, uri));

        // Cleanup.
        assertTrue(FileUtils.deleteContentsAndDir(sliceDir));
    }

    @Test
    public void testBackup() throws XmlPullParserException, IOException {
        File sliceDir = new File(mContext.getCacheDir(), "testBackupSlices");
        Log.v(TAG, "testBackup: slice permissions stored in " + sliceDir.getAbsolutePath());
        Uri uri = new Builder().scheme(ContentResolver.SCHEME_CONTENT)
                .authority("authority")
                .path("something").build();
        SlicePermissionManager permissions = new SlicePermissionManager(mContext,
                TestableLooper.get(this).getLooper(), sliceDir);

        permissions.grantFullAccess("com.android.mypkg", 10);
        permissions.grantSliceAccess("com.android.otherpkg", 0, "com.android.lastpkg", 1, uri);

        ByteArrayOutputStream output = new ByteArrayOutputStream();
        XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
        serializer.setOutput(output, Encoding.UTF_8.name());


        TestableLooper.get(this).processAllMessages();
        permissions.writeBackup(serializer);
        serializer.flush();

        ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
        parser.setInput(input, Encoding.UTF_8.name());

        permissions = new SlicePermissionManager(mContext,
                TestableLooper.get(this).getLooper());
        permissions.readRestore(parser);

        if (!permissions.hasFullAccess("com.android.mypkg", 10)) {
            fail("com.android.mypkg@10 did not have full access. backup file: "
                    + output.toString());
        }
        assertTrue(permissions.hasPermission("com.android.otherpkg", 0,
                ContentProvider.maybeAddUserId(uri, 1)));
        permissions.removePkg("com.android.lastpkg", 1);
        assertFalse(permissions.hasPermission("com.android.otherpkg", 0,
                ContentProvider.maybeAddUserId(uri, 1)));

        // Cleanup.
        assertTrue(FileUtils.deleteContentsAndDir(sliceDir));
    }

    @Test
    public void testInvalid() {
        File sliceDir = new File(mContext.getCacheDir(), "testInvalidSlices");
        Log.v(TAG, "testInvalid: slice permissions stored in " + sliceDir.getAbsolutePath());
        if (!sliceDir.exists()) {
            sliceDir.mkdir();
        }
        SlicePermissionManager permissions = new SlicePermissionManager(mContext,
                TestableLooper.get(this).getLooper(), sliceDir);

        DirtyTracker.Persistable junk = new DirtyTracker.Persistable() {
            @Override
            public String getFileName() {
                return "invalidData";
            }

            @Override
            public void writeTo(XmlSerializer out) throws IOException {
                throw new RuntimeException("this RuntimeException inside junk.writeTo() "
                        + "should be caught and suppressed by surrounding code");
            }
        };

        // let's put something bad in here
        permissions.addDirtyImmediate(junk);
        // force a persist. if this throws, it would take down system_server
        permissions.handlePersist();

        // Cleanup.
        assertTrue(FileUtils.deleteContentsAndDir(sliceDir));
    }

}
