1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.providers.tv;
18
19import com.google.android.collect.Sets;
20
21import android.content.ContentUris;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.ProviderInfo;
26import android.database.Cursor;
27import android.media.tv.TvContract;
28import android.media.tv.TvContract.Channels;
29import android.media.tv.TvContract.PreviewPrograms;
30import android.net.Uri;
31import android.os.Bundle;
32import android.provider.Settings;
33import android.test.AndroidTestCase;
34import android.test.mock.MockContentProvider;
35import android.test.mock.MockContentResolver;
36import android.util.Log;
37
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.Collection;
41import java.util.HashSet;
42import java.util.Objects;
43import java.util.Set;
44
45public class TransientRowHelperTests extends AndroidTestCase {
46    private static final String FAKE_INPUT_ID = "TransientRowHelperTests";
47
48    private MockContentResolver mResolver;
49    private TvProviderForTesting mProvider;
50    private RebootSimulatingTransientRowHelper mTransientRowHelper;
51
52    @Override
53    protected void setUp() throws Exception {
54        super.setUp();
55
56        mResolver = new MockContentResolver();
57        mResolver.addProvider(Settings.AUTHORITY, new MockContentProvider() {
58            @Override
59            public Bundle call(String method, String request, Bundle args) {
60                return new Bundle();
61            }
62        });
63
64        mProvider = new TvProviderForTesting();
65        mResolver.addProvider(TvContract.AUTHORITY, mProvider);
66
67        setContext(new MockTvProviderContext(mResolver, getContext()));
68
69        final ProviderInfo info = new ProviderInfo();
70        info.authority = TvContract.AUTHORITY;
71        mProvider.attachInfoForTesting(getContext(), info);
72        mTransientRowHelper = new RebootSimulatingTransientRowHelper(getContext());
73        mProvider.setTransientRowHelper(mTransientRowHelper);
74        Utils.clearTvProvider(mResolver);
75    }
76
77    @Override
78    protected void tearDown() throws Exception {
79        Utils.clearTvProvider(mResolver);
80        mProvider.shutdown();
81        super.tearDown();
82    }
83
84    private static class PreviewProgram {
85        long id;
86        final boolean isTransient;
87
88        PreviewProgram(boolean isTransient) {
89            this(-1, isTransient);
90        }
91
92        PreviewProgram(long id, boolean isTransient) {
93            this.id = id;
94            this.isTransient = isTransient;
95        }
96
97        @Override
98        public boolean equals(Object obj) {
99            if (!(obj instanceof PreviewProgram)) {
100                return false;
101            }
102            PreviewProgram that = (PreviewProgram) obj;
103            return Objects.equals(id, that.id)
104                    && Objects.equals(isTransient, that.isTransient);
105        }
106
107        @Override
108        public int hashCode() {
109            return Objects.hash(id, isTransient);
110        }
111
112        @Override
113        public String toString() {
114            return "PreviewProgram(id=" + id + ",isTransient=" + isTransient + ")";
115        }
116    }
117
118    private long insertChannel(boolean isTransient) {
119        ContentValues values = new ContentValues();
120        values.put(Channels.COLUMN_INPUT_ID, FAKE_INPUT_ID);
121        values.put(Channels.COLUMN_TRANSIENT, isTransient ? 1 : 0);
122        Uri uri = mResolver.insert(Channels.CONTENT_URI, values);
123        assertNotNull(uri);
124        return ContentUris.parseId(uri);
125    }
126
127    private void insertPreviewPrograms(long channelId, PreviewProgram... programs) {
128        insertPreviewPrograms(channelId, Arrays.asList(programs));
129    }
130
131    private void insertPreviewPrograms(long channelId, Collection<PreviewProgram> programs) {
132        ContentValues values = new ContentValues();
133        values.put(PreviewPrograms.COLUMN_CHANNEL_ID, channelId);
134        for (PreviewProgram program : programs) {
135            values.put(PreviewPrograms.COLUMN_TYPE, PreviewPrograms.TYPE_MOVIE);
136            values.put(PreviewPrograms.COLUMN_TRANSIENT, program.isTransient ? 1 : 0);
137            Uri uri = mResolver.insert(PreviewPrograms.CONTENT_URI, values);
138            assertNotNull(uri);
139            program.id = ContentUris.parseId(uri);
140        }
141    }
142
143    private Set<PreviewProgram> queryPreviewPrograms() {
144        String[] projection = new String[] {
145            PreviewPrograms._ID,
146            PreviewPrograms.COLUMN_TRANSIENT,
147        };
148
149        Cursor cursor = mResolver.query(PreviewPrograms.CONTENT_URI, projection, null, null, null);
150        assertNotNull(cursor);
151        try {
152            Set<PreviewProgram> programs = Sets.newHashSet();
153            while (cursor.moveToNext()) {
154                programs.add(new PreviewProgram(cursor.getLong(0), cursor.getInt(1) == 1));
155            }
156            return programs;
157        } finally {
158            cursor.close();
159        }
160    }
161
162    private long getChannelCount() {
163        String[] projection = new String[] {
164            Channels._ID,
165        };
166
167        Cursor cursor = mResolver.query(Channels.CONTENT_URI, projection, null, null, null);
168        assertNotNull(cursor);
169        try {
170            return cursor.getCount();
171        } finally {
172            cursor.close();
173        }
174    }
175
176    public void testTransientRowsAreDeletedAfterReboot() {
177        PreviewProgram transientProgramInTransientChannel =
178                new PreviewProgram(true /* transient */);
179        PreviewProgram permanentProgramInTransientChannel =
180                new PreviewProgram(false /* transient */);
181        PreviewProgram transientProgramInPermanentChannel =
182                new PreviewProgram(true /* transient */);
183        PreviewProgram permanentProgramInPermanentChannel =
184                new PreviewProgram(false /* transient */);
185        long transientChannelId = insertChannel(true /* transient */);
186        long permanentChannelId = insertChannel(false /* transient */);
187        insertPreviewPrograms(transientChannelId, transientProgramInTransientChannel);
188        insertPreviewPrograms(transientChannelId, permanentProgramInTransientChannel);
189        insertPreviewPrograms(permanentChannelId, transientProgramInPermanentChannel);
190        insertPreviewPrograms(permanentChannelId, permanentProgramInPermanentChannel);
191
192        assertEquals("Before reboot all the programs inserted should exist.",
193                Sets.newHashSet(transientProgramInTransientChannel,
194                        permanentProgramInTransientChannel, transientProgramInPermanentChannel,
195                        permanentProgramInPermanentChannel),
196                queryPreviewPrograms());
197        assertEquals("Before reboot the channels inserted should exist.",
198                2, getChannelCount());
199
200        mTransientRowHelper.simulateReboot();
201        assertEquals("Transient program and programs in transient channel should be removed.",
202                Sets.newHashSet(permanentProgramInPermanentChannel), queryPreviewPrograms());
203        assertEquals("Transient channel should not be removed.",
204                1, getChannelCount());
205    }
206
207    private class RebootSimulatingTransientRowHelper extends TransientRowHelper {
208        private int mLastDeletionBootCount;
209        private int mBootCount = 1;
210
211        private RebootSimulatingTransientRowHelper(Context context) {
212            super(context);
213        }
214
215        @Override
216        protected int getBootCount() {
217            return mBootCount;
218        }
219
220        @Override
221        protected int getLastDeletionBootCount() {
222            return mLastDeletionBootCount;
223        }
224
225        @Override
226        protected void setLastDeletionBootCount() {
227            mLastDeletionBootCount = mBootCount;
228        }
229
230        private void simulateReboot() {
231            mTransientRowsDeleted = false;
232            mBootCount++;
233        }
234    }
235}
236