/*
 * Copyright 2013 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.
 */

#include <binder/Parcel.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <binder/TextOutput.h>
#include <cutils/ashmem.h>

#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace android;

void writeString16(Parcel& parcel, const char* string)
{
    if (string != nullptr)
    {
        parcel.writeString16(String16(string));
    }
    else
    {
        parcel.writeInt32(-1);
    }
}

// get the name of the generic interface we hold a reference to
static String16 get_interface_name(sp<IBinder> service)
{
    if (service != nullptr) {
        Parcel data, reply;
        status_t err = service->transact(IBinder::INTERFACE_TRANSACTION, data, &reply);
        if (err == NO_ERROR) {
            return reply.readString16();
        }
    }
    return String16();
}

static String8 good_old_string(const String16& src)
{
    String8 name8;
    char ch8[2];
    ch8[1] = 0;
    for (unsigned j = 0; j < src.size(); j++) {
        char16_t ch = src[j];
        if (ch < 128) ch8[0] = (char)ch;
        name8.append(ch8);
    }
    return name8;
}

int main(int argc, char* const argv[])
{
    bool wantsUsage = false;
    int result = 0;

    while (1) {
        int ic = getopt(argc, argv, "h?");
        if (ic < 0)
            break;

        switch (ic) {
        case 'h':
        case '?':
            wantsUsage = true;
            break;
        default:
            aerr << "service: Unknown option -" << ic << endl;
            wantsUsage = true;
            result = 10;
            break;
        }
    }
#ifdef VENDORSERVICES
    ProcessState::initWithDriver("/dev/vndbinder");
#endif
    sp<IServiceManager> sm = defaultServiceManager();
    fflush(stdout);
    if (sm == nullptr) {
        aerr << "service: Unable to get default service manager!" << endl;
        return 20;
    }

    if (optind >= argc) {
        wantsUsage = true;
    } else if (!wantsUsage) {
        if (strcmp(argv[optind], "check") == 0) {
            optind++;
            if (optind < argc) {
                sp<IBinder> service = sm->checkService(String16(argv[optind]));
                aout << "Service " << argv[optind] <<
                    (service == nullptr ? ": not found" : ": found") << endl;
            } else {
                aerr << "service: No service specified for check" << endl;
                wantsUsage = true;
                result = 10;
            }
        }
        else if (strcmp(argv[optind], "list") == 0) {
            Vector<String16> services = sm->listServices();
            aout << "Found " << services.size() << " services:" << endl;
            for (unsigned i = 0; i < services.size(); i++) {
                String16 name = services[i];
                sp<IBinder> service = sm->checkService(name);
                aout << i
                     << "\t" << good_old_string(name)
                     << ": [" << good_old_string(get_interface_name(service)) << "]"
                     << endl;
            }
        } else if (strcmp(argv[optind], "call") == 0) {
            optind++;
            if (optind+1 < argc) {
                int serviceArg = optind;
                sp<IBinder> service = sm->checkService(String16(argv[optind++]));
                String16 ifName = get_interface_name(service);
                int32_t code = atoi(argv[optind++]);
                if (service != nullptr && ifName.size() > 0) {
                    Parcel data, reply;

                    // the interface name is first
                    data.writeInterfaceToken(ifName);

                    // then the rest of the call arguments
                    while (optind < argc) {
                        if (strcmp(argv[optind], "i32") == 0) {
                            optind++;
                            if (optind >= argc) {
                                aerr << "service: no integer supplied for 'i32'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            data.writeInt32(atoi(argv[optind++]));
                        } else if (strcmp(argv[optind], "i64") == 0) {
                            optind++;
                            if (optind >= argc) {
                                aerr << "service: no integer supplied for 'i64'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            data.writeInt64(atoll(argv[optind++]));
                        } else if (strcmp(argv[optind], "s16") == 0) {
                            optind++;
                            if (optind >= argc) {
                                aerr << "service: no string supplied for 's16'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            data.writeString16(String16(argv[optind++]));
                        } else if (strcmp(argv[optind], "f") == 0) {
                            optind++;
                            if (optind >= argc) {
                                aerr << "service: no number supplied for 'f'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            data.writeFloat(atof(argv[optind++]));
                        } else if (strcmp(argv[optind], "d") == 0) {
                            optind++;
                            if (optind >= argc) {
                                aerr << "service: no number supplied for 'd'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            data.writeDouble(atof(argv[optind++]));
                        } else if (strcmp(argv[optind], "null") == 0) {
                            optind++;
                            data.writeStrongBinder(nullptr);
                        } else if (strcmp(argv[optind], "fd") == 0) {
                            optind++;
                            if (optind >= argc) {
                                aerr << "service: no path supplied for 'fd'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            const char *path = argv[optind++];
                            int fd = open(path, O_RDONLY);
                            if (fd < 0) {
                                aerr << "service: could not open '" << path << "'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            data.writeFileDescriptor(fd, true /* take ownership */);
                        } else if (strcmp(argv[optind], "afd") == 0) {
                            optind++;
                            if (optind >= argc) {
                                aerr << "service: no path supplied for 'afd'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            const char *path = argv[optind++];
                            int fd = open(path, O_RDONLY);
                            struct stat statbuf;
                            if (fd < 0 || fstat(fd, &statbuf) != 0) {
                                aerr << "service: could not open or stat '" << path << "'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            int afd = ashmem_create_region("test", statbuf.st_size);
                            void* ptr = mmap(NULL, statbuf.st_size,
                                   PROT_READ | PROT_WRITE, MAP_SHARED, afd, 0);
                            read(fd, ptr, statbuf.st_size);
                            close(fd);
                            data.writeFileDescriptor(afd, true /* take ownership */);
                        } else if (strcmp(argv[optind], "nfd") == 0) {
                            optind++;
                            if (optind >= argc) {
                                aerr << "service: no file descriptor supplied for 'nfd'" << endl;
                                wantsUsage = true;
                                result = 10;
                                break;
                            }
                            data.writeFileDescriptor(
                                    atoi(argv[optind++]), true /* take ownership */);

                        } else if (strcmp(argv[optind], "intent") == 0) {

                            char* action = nullptr;
                            char* dataArg = nullptr;
                            char* type = nullptr;
                            int launchFlags = 0;
                            char* component = nullptr;
                            int categoryCount = 0;
                            char* categories[16];

                            char* context1 = nullptr;

                            optind++;

                            while (optind < argc)
                            {
                                char* key = strtok_r(argv[optind], "=", &context1);
                                char* value = strtok_r(nullptr, "=", &context1);

                                // we have reached the end of the XXX=XXX args.
                                if (key == nullptr) break;

                                if (strcmp(key, "action") == 0)
                                {
                                    action = value;
                                }
                                else if (strcmp(key, "data") == 0)
                                {
                                    dataArg = value;
                                }
                                else if (strcmp(key, "type") == 0)
                                {
                                    type = value;
                                }
                                else if (strcmp(key, "launchFlags") == 0)
                                {
                                    launchFlags = atoi(value);
                                }
                                else if (strcmp(key, "component") == 0)
                                {
                                    component = value;
                                }
                                else if (strcmp(key, "categories") == 0)
                                {
                                    char* context2 = nullptr;
                                    categories[categoryCount] = strtok_r(value, ",", &context2);

                                    while (categories[categoryCount] != nullptr)
                                    {
                                        categoryCount++;
                                        categories[categoryCount] = strtok_r(nullptr, ",", &context2);
                                    }
                                }

                                optind++;
                            }

                            writeString16(data, action);
                            writeString16(data, dataArg);
                            writeString16(data, type);
                            data.writeInt32(launchFlags);
                            writeString16(data, component);

                            if (categoryCount > 0)
                            {
                                data.writeInt32(categoryCount);
                                for (int i = 0 ; i < categoryCount ; i++)
                                {
                                    writeString16(data, categories[i]);
                                }
                            }
                            else
                            {
                                data.writeInt32(0);
                            }

                            // for now just set the extra field to be null.
                            data.writeInt32(-1);
                        } else {
                            aerr << "service: unknown option " << argv[optind] << endl;
                            wantsUsage = true;
                            result = 10;
                            break;
                        }
                    }

                    service->transact(code, data, &reply);
                    aout << "Result: " << reply << endl;
                } else {
                    aerr << "service: Service " << argv[serviceArg]
                        << " does not exist" << endl;
                    result = 10;
                }
            } else {
                if (optind < argc) {
                    aerr << "service: No service specified for call" << endl;
                } else {
                    aerr << "service: No code specified for call" << endl;
                }
                wantsUsage = true;
                result = 10;
            }
        } else {
            aerr << "service: Unknown command " << argv[optind] << endl;
            wantsUsage = true;
            result = 10;
        }
    }

    if (wantsUsage) {
        aout << "Usage: service [-h|-?]\n"
                "       service list\n"
                "       service check SERVICE\n"
                "       service call SERVICE CODE [i32 N | i64 N | f N | d N | s16 STR | null"
                " | fd f | nfd n | afd f ] ...\n"
                "Options:\n"
                "   i32: Write the 32-bit integer N into the send parcel.\n"
                "   i64: Write the 64-bit integer N into the send parcel.\n"
                "   f:   Write the 32-bit single-precision number N into the send parcel.\n"
                "   d:   Write the 64-bit double-precision number N into the send parcel.\n"
                "   s16: Write the UTF-16 string STR into the send parcel.\n"
                "  null: Write a null binder into the send parcel.\n"
                "    fd: Write a file descriptor for the file f to the send parcel.\n"
                "   nfd: Write file descriptor n to the send parcel.\n"
                "   afd: Write an ashmem file descriptor for a region containing the data from"
                " file f to the send parcel.\n";
//                "   intent: Write and Intent int the send parcel. ARGS can be\n"
//                "       action=STR data=STR type=STR launchFlags=INT component=STR categories=STR[,STR,...]\n";
        return result;
    }

    return result;
}

