1248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank/*******************************************************************************
2248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      Copyright (C) 2012 Google Inc.
3248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      Licensed to The Android Open Source Project.
4248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
5248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      Licensed under the Apache License, Version 2.0 (the "License");
6248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      you may not use this file except in compliance with the License.
7248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      You may obtain a copy of the License at
8248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
9248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *           http://www.apache.org/licenses/LICENSE-2.0
10248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
11248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      Unless required by applicable law or agreed to in writing, software
12248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      distributed under the License is distributed on an "AS IS" BASIS,
13248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      See the License for the specific language governing permissions and
15248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *      limitations under the License.
16248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *******************************************************************************/
17248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
18248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankpackage com.android.mail.browse;
19248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
20248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport android.content.ContentProvider;
21248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport android.content.ContentValues;
22248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport android.content.Context;
23248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport android.content.UriMatcher;
24b96eb04b2a8ff4b7d47b09b4b2b5493c032e4d36Tomasz Mikolajewskiimport android.content.pm.ProviderInfo;
25248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport android.database.Cursor;
26248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport android.database.MatrixCursor;
27248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport android.net.Uri;
28248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
29384b8de1a1e18b29fe392151b06ce6fbe07be4bdScott Kennedyimport com.android.mail.utils.MatrixCursorWithCachedColumns;
30384b8de1a1e18b29fe392151b06ce6fbe07be4bdScott Kennedy
31248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport java.util.ArrayList;
32248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport java.util.HashMap;
33248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankimport java.util.Map.Entry;
34384b8de1a1e18b29fe392151b06ce6fbe07be4bdScott Kennedyimport java.util.Set;
35248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
36248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank/**
37248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * TestProvider is a ContentProvider that can be used to simulate the storage and retrieval of
38248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * rows from any ContentProvider, even if that provider does not exist in the caller's package.
39248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
40248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * It is specifically designed to enable testing of sync adapters that create
41248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * ContentProviderOperations (CPOs) that are then executed using ContentResolver.applyBatch().
42248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * Why is this useful? Because we can't instantiate CalendarProvider or ContactsProvider from our
43248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * package, as required by MockContentResolver.addProvider()
44248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
45248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * Usage:
46248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
47248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * ContentResolver.applyBatch(MockProvider.AUTHORITY, batch) will cause the CPOs to be executed,
48248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * returning an array of ContentProviderResult; in the case of inserts, the result will include
49248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * a Uri that can be used via query(). Note that the CPOs can contain references to any authority.
50248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
51248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * query() does not allow non-null selection, selectionArgs, or sortOrder arguments; the
52248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * presence of these will result in an UnsupportedOperationException insert() acts as expected,
53248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * returning a Uri that can be directly used in a query
54248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
55248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * delete() and update() do not allow non-null selection or selectionArgs arguments; the presence
56248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * of these will result in an UnsupportedOperationException
57248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
58248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * NOTE: MockProvider only simulates direct storage/retrieval of rows; it does not (and can not)
59248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * simulate other actions (e.g. creation of ancillary data) that the actual provider might perform
60248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank *
61248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank * NOTE: See MockProviderTests for usage examples
62248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank **/
63248b1b49455785aa2fa426bc28090547abfcb01aMarc Blankpublic class TestProvider extends ContentProvider {
64248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public static final String AUTHORITY = "com.android.mail.mock.provider";
65b96eb04b2a8ff4b7d47b09b4b2b5493c032e4d36Tomasz Mikolajewski
66248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    /* package */static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
67248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
68248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    /* package */static final int TABLE = 100;
69248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    /* package */static final int RECORD = 101;
70248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
71248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public static final String ID_COLUMN = "_id";
72248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
73248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public TestProvider() {
74248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        super();
75248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    }
76248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
77248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public TestProvider(Context context) {
78248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        this();
79b96eb04b2a8ff4b7d47b09b4b2b5493c032e4d36Tomasz Mikolajewski        ProviderInfo info = new ProviderInfo();
80b96eb04b2a8ff4b7d47b09b4b2b5493c032e4d36Tomasz Mikolajewski        info.authority = AUTHORITY;
81b96eb04b2a8ff4b7d47b09b4b2b5493c032e4d36Tomasz Mikolajewski        attachInfo(context, info);
82248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    }
83248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
84248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    // We'll store our values here
85248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    private HashMap<String, ContentValues> mMockStore = new HashMap<String, ContentValues>();
86248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    // And we'll generate new id's from here
87248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    long mMockId = 1;
88248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
89248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    @Override
90248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public int delete(Uri uri, String selection, String[] selectionArgs) {
91248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        if (selection != null || selectionArgs != null) {
92248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            throw new UnsupportedOperationException();
93248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
94248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        String path = uri.getPath();
95248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        if (mMockStore.containsKey(path)) {
96248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            mMockStore.remove(path);
97248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            return 1;
98248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        } else {
99248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            return 0;
100248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
101248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    }
102248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
103248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    @Override
104248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public String getType(Uri uri) {
105248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        throw new UnsupportedOperationException();
106248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    }
107248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
108248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    @Override
109248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public Uri insert(Uri uri, ContentValues values) {
110248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        // Remove the leading slash
111248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        String table = uri.getPath().substring(1);
112248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        long id = mMockId++;
113248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        Uri newUri = new Uri.Builder().scheme("content").authority(AUTHORITY).path(table)
114248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                .appendPath(Long.toString(id)).build();
115248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        // Remember to store the _id
116248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        values.put(ID_COLUMN, id);
117248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        mMockStore.put(newUri.getPath(), values);
118248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        int match = sURIMatcher.match(uri);
119248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        if (match == UriMatcher.NO_MATCH) {
120248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            sURIMatcher.addURI(AUTHORITY, table, TABLE);
121248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            sURIMatcher.addURI(AUTHORITY, table + "/#", RECORD);
122248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
123248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        return newUri;
124248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    }
125248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
126248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    @Override
127248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public boolean onCreate() {
128248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        return false;
129248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    }
130248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
131248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    @Override
132248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
133248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            String sortOrder) {
134248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        if (selection != null || selectionArgs != null || sortOrder != null || projection == null) {
135248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            throw new UnsupportedOperationException();
136248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
137b96eb04b2a8ff4b7d47b09b4b2b5493c032e4d36Tomasz Mikolajewski        final int match = sURIMatcher.match(uri);
138248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        ArrayList<ContentValues> valuesList = new ArrayList<ContentValues>();
139248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        switch (match) {
140248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            case TABLE:
141248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet();
142248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                String prefix = uri.getPath() + "/";
143248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                for (Entry<String, ContentValues> entry : entrySet) {
144248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    if (entry.getKey().startsWith(prefix)) {
145248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                        valuesList.add(entry.getValue());
146248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    }
147248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                }
148248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                break;
149248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            case RECORD:
150248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                ContentValues values = mMockStore.get(uri.getPath());
151248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                if (values != null) {
152248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    valuesList.add(values);
153248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                }
154248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                break;
155248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            default:
156248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                throw new IllegalArgumentException("Unknown URI " + uri);
157248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
158384b8de1a1e18b29fe392151b06ce6fbe07be4bdScott Kennedy        MatrixCursor cursor = new MatrixCursorWithCachedColumns(projection, 1);
159248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        for (ContentValues cv : valuesList) {
160248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            Object[] rowValues = new Object[projection.length];
161248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            int i = 0;
162248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            for (String column : projection) {
163248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                rowValues[i++] = cv.get(column);
164248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            }
165248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            cursor.addRow(rowValues);
166248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
167248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        return cursor;
168248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    }
169248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank
170248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    @Override
171248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    public int update(Uri uri, ContentValues newValues, String selection, String[] selectionArgs) {
172248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        if (selection != null || selectionArgs != null) {
173248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            throw new UnsupportedOperationException();
174248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
175b96eb04b2a8ff4b7d47b09b4b2b5493c032e4d36Tomasz Mikolajewski        final int match = sURIMatcher.match(uri);
176248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        ArrayList<ContentValues> updateValuesList = new ArrayList<ContentValues>();
177248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        String path = uri.getPath();
178248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        switch (match) {
179248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            case TABLE:
180248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                Set<Entry<String, ContentValues>> entrySet = mMockStore.entrySet();
181248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                String prefix = path + "/";
182248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                for (Entry<String, ContentValues> entry : entrySet) {
183248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    if (entry.getKey().startsWith(prefix)) {
184248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                        updateValuesList.add(entry.getValue());
185248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    }
186248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                }
187248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                break;
188248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            case RECORD:
189248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                ContentValues cv = mMockStore.get(path);
190248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                if (cv != null) {
191248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    updateValuesList.add(cv);
192248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                }
193248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                break;
194248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            default:
195248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                throw new IllegalArgumentException("Unknown URI " + uri);
196248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
197248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        Set<Entry<String, Object>> newValuesSet = newValues.valueSet();
198248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        for (Entry<String, Object> entry : newValuesSet) {
199248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            String key = entry.getKey();
200248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            Object value = entry.getValue();
201248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            for (ContentValues targetValues : updateValuesList) {
202248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                if (value instanceof Integer) {
203248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    targetValues.put(key, (Integer) value);
204248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                } else if (value instanceof Long) {
205248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    targetValues.put(key, (Long) value);
206248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                } else if (value instanceof String) {
207248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    targetValues.put(key, (String) value);
208248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                } else if (value instanceof Boolean) {
209248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    targetValues.put(key, (Boolean) value);
210248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                } else {
211248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    throw new IllegalArgumentException();
212248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                }
213248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            }
214248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
215248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        for (ContentValues targetValues : updateValuesList) {
216248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            switch(match) {
217248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                case TABLE:
218248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    mMockStore.put(path + "/" + targetValues.getAsLong(ID_COLUMN), targetValues);
219248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    break;
220248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                case RECORD:
221248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    mMockStore.put(path, targetValues);
222248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank                    break;
223248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank            }
224248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        }
225248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank        return updateValuesList.size();
226248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank    }
227248b1b49455785aa2fa426bc28090547abfcb01aMarc Blank}
228