Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 31dc634b authored by Jae Seo's avatar Jae Seo
Browse files

Log watched TV programs

Change-Id: Id6ee87dffcf90f10f1619e849126c66ad27464e2
parent 6689c970
Loading
Loading
Loading
Loading
+203 −4
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ package com.android.server.tv;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -26,13 +29,18 @@ import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.TvContract;
import android.tv.ITvInputClient;
import android.tv.ITvInputManager;
import android.tv.ITvInputService;
@@ -41,13 +49,14 @@ import android.tv.ITvInputSession;
import android.tv.ITvInputSessionCallback;
import android.tv.TvInputInfo;
import android.tv.TvInputService;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Surface;

import com.android.internal.content.PackageMonitor;
import com.android.internal.os.SomeArgs;
import com.android.server.IoThread;
import com.android.server.SystemService;

import java.util.ArrayList;
@@ -63,6 +72,8 @@ public final class TvInputManagerService extends SystemService {

    private final Context mContext;

    private final ContentResolver mContentResolver;

    // A global lock.
    private final Object mLock = new Object();

@@ -72,10 +83,17 @@ public final class TvInputManagerService extends SystemService {
    // A map from user id to UserState.
    private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();

    private final Handler mLogHandler;

    public TvInputManagerService(Context context) {
        super(context);

        mContext = context;
        mContentResolver = context.getContentResolver();
        mLogHandler = new LogHandler(IoThread.get().getLooper());

        registerBroadcastReceivers();

        synchronized (mLock) {
            mUserStates.put(mCurrentUserId, new UserState());
            buildTvInputListLocked(mCurrentUserId);
@@ -325,6 +343,14 @@ public final class TvInputManagerService extends SystemService {
        UserState userState = getUserStateLocked(userId);
        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);

        // Close the open log entry, if any.
        if (sessionState.logUri != null) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = sessionState.logUri;
            args.arg2 = System.currentTimeMillis();
            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
        }

        // Also remove the session token from the session token list of the current service.
        ServiceState serviceState = userState.serviceStateMap.get(sessionState.name);
        if (serviceState != null) {
@@ -384,7 +410,7 @@ public final class TvInputManagerService extends SystemService {
                    UserState userState = getUserStateLocked(resolvedUserId);
                    ServiceState serviceState = userState.serviceStateMap.get(name);
                    if (serviceState == null) {
                        serviceState = new ServiceState(name, resolvedUserId);
                        serviceState = new ServiceState(resolvedUserId);
                        userState.serviceStateMap.put(name, serviceState);
                    }
                    IBinder iBinder = client.asBinder();
@@ -468,7 +494,7 @@ public final class TvInputManagerService extends SystemService {
                    // Also, add them to the session state map of the current service.
                    ServiceState serviceState = userState.serviceStateMap.get(name);
                    if (serviceState == null) {
                        serviceState = new ServiceState(name, resolvedUserId);
                        serviceState = new ServiceState(resolvedUserId);
                        userState.serviceStateMap.put(name, serviceState);
                    }
                    serviceState.sessionTokens.add(sessionToken);
@@ -557,6 +583,35 @@ public final class TvInputManagerService extends SystemService {
                synchronized (mLock) {
                    try {
                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);

                        long currentTime = System.currentTimeMillis();
                        long channelId = ContentUris.parseId(channelUri);

                        // Close the open log entry first, if any.
                        UserState userState = getUserStateLocked(resolvedUserId);
                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
                        if (sessionState.logUri != null) {
                            SomeArgs args = SomeArgs.obtain();
                            args.arg1 = sessionState.logUri;
                            args.arg2 = currentTime;
                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
                                    .sendToTarget();
                        }

                        // Create a log entry and fill it later.
                        ContentValues values = new ContentValues();
                        values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
                                currentTime);
                        values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, 0);
                        values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);

                        sessionState.logUri = mContentResolver.insert(
                                TvContract.WatchedPrograms.CONTENT_URI, values);
                        SomeArgs args = SomeArgs.obtain();
                        args.arg1 = sessionState.logUri;
                        args.arg2 = ContentUris.parseId(channelUri);
                        args.arg3 = currentTime;
                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
                    } catch (RemoteException e) {
                        Slog.e(TAG, "error in tune", e);
                        return;
@@ -652,7 +707,7 @@ public final class TvInputManagerService extends SystemService {
        private boolean bound;
        private boolean available;

        private ServiceState(ComponentName name, int userId) {
        private ServiceState(int userId) {
            this.connection = new InputServiceConnection(userId);
        }
    }
@@ -664,6 +719,7 @@ public final class TvInputManagerService extends SystemService {
        private final int callingUid;

        private ITvInputSession session;
        private Uri logUri;

        private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) {
            this.name = name;
@@ -738,4 +794,147 @@ public final class TvInputManagerService extends SystemService {
            }
        }
    }

    private final class LogHandler extends Handler {
        private static final int MSG_OPEN_ENTRY = 1;
        private static final int MSG_UPDATE_ENTRY = 2;
        private static final int MSG_CLOSE_ENTRY = 3;

        public LogHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_OPEN_ENTRY: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    Uri uri = (Uri) args.arg1;
                    long channelId = (long) args.arg2;
                    long time = (long) args.arg3;
                    onOpenEntry(uri, channelId, time);
                    args.recycle();
                    return;
                }
                case MSG_UPDATE_ENTRY: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    Uri uri = (Uri) args.arg1;
                    long channelId = (long) args.arg2;
                    long time = (long) args.arg3;
                    onUpdateEntry(uri, channelId, time);
                    args.recycle();
                    return;
                }
                case MSG_CLOSE_ENTRY: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    Uri uri = (Uri) args.arg1;
                    long time = (long) args.arg2;
                    onCloseEntry(uri, time);
                    args.recycle();
                    return;
                }
                default: {
                    Log.w(TAG, "Unhandled message code: " + msg.what);
                    return;
                }
            }
        }

        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
            String[] projection = {
                    TvContract.Programs.TITLE,
                    TvContract.Programs.START_TIME_UTC_MILLIS,
                    TvContract.Programs.END_TIME_UTC_MILLIS,
                    TvContract.Programs.DESCRIPTION
            };
            String selection = TvContract.Programs.CHANNEL_ID + "=? AND "
                    + TvContract.Programs.START_TIME_UTC_MILLIS + "<=? AND "
                    + TvContract.Programs.END_TIME_UTC_MILLIS + ">?";
            String[] selectionArgs = {
                    String.valueOf(channelId),
                    String.valueOf(watchStarttime),
                    String.valueOf(watchStarttime)
            };
            String sortOrder = TvContract.Programs.START_TIME_UTC_MILLIS + " ASC";
            Cursor cursor = null;
            try {
                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
                        selection, selectionArgs, sortOrder);
                if (cursor != null && cursor.moveToNext()) {
                    ContentValues values = new ContentValues();
                    values.put(TvContract.WatchedPrograms.TITLE, cursor.getString(0));
                    values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, cursor.getLong(1));
                    long endTime = cursor.getLong(2);
                    values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
                    values.put(TvContract.WatchedPrograms.DESCRIPTION, cursor.getString(3));
                    mContentResolver.update(uri, values, null, null);

                    // Schedule an update when the current program ends.
                    SomeArgs args = SomeArgs.obtain();
                    args.arg1 = uri;
                    args.arg2 = channelId;
                    args.arg3 = endTime;
                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
                }
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }

        private void onUpdateEntry(Uri uri, long channelId, long time) {
            String[] projection = {
                    TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
                    TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS,
                    TvContract.WatchedPrograms.TITLE,
                    TvContract.WatchedPrograms.START_TIME_UTC_MILLIS,
                    TvContract.WatchedPrograms.END_TIME_UTC_MILLIS,
                    TvContract.WatchedPrograms.DESCRIPTION
            };
            Cursor cursor = null;
            try {
                cursor = mContentResolver.query(uri, projection, null, null, null);
                if (cursor != null && cursor.moveToNext()) {
                    long watchStartTime = cursor.getLong(0);
                    long watchEndTime = cursor.getLong(1);
                    String title = cursor.getString(2);
                    long startTime = cursor.getLong(3);
                    long endTime = cursor.getLong(4);
                    String description = cursor.getString(5);

                    // Do nothing if the current log entry is already closed.
                    if (watchEndTime > 0) {
                        return;
                    }

                    // The current program has just ended. Create a (complete) log entry off the
                    // current entry.
                    ContentValues values = new ContentValues();
                    values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
                            watchStartTime);
                    values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, time);
                    values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);
                    values.put(TvContract.WatchedPrograms.TITLE, title);
                    values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, startTime);
                    values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
                    values.put(TvContract.WatchedPrograms.DESCRIPTION, description);
                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
                }
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
            // Re-open the current log entry with the next program information.
            onOpenEntry(uri, channelId, time);
        }

        private void onCloseEntry(Uri uri, long watchEndTime) {
            ContentValues values = new ContentValues();
            values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, watchEndTime);
            mContentResolver.update(uri, values, null, null);
        }
    }
}